今回の内容について
今回の内容は、オブジェクト思考によるプログラミング言語の基本である「クラス」について学びます。
Javaの「クラス」ってなんなんだ?
まず、クラスの前に、
「オブジェクト思考とはなんなのか???」
ということがなんなのか、から始めてみましょう。
オブジェクト思考の原点
オブジェクト思考の考え方で一番大事なことが**「役割」**です。
プログラムというものは、ユーザーからすると大きく一つのものとして動きます。
例えば、みなさんがよく遊ぶスマホゲームも、アイコン1つで済まされていますが、
裏ではたくさんのプログラムが動いています。
例えば、リズムゲームなら
- 譜面情報
- 難易度
- スタミナ
- 課金情報
- ガチャ
というように、様々な要素がありますよね。
もちろん、これを全て一つのファイルにしてプログラムを進めることができますが、
そんなことしたら大変なことになるのはすぐに理解できてしまうかと思います。
オブジェクト思考は、これらの要素を「役割」ごとに分離することに目的があります。
役割の分離によって便利なこと
役割ごとにクラスを分離することによる一番の利点は「分かりやすくなる」ということです。
ゲームの進行に関するクラスを以下の通りに分けたとします。
- 譜面再生
- スタミナ管理
- ガチャ
- 設定
例えば、以下のような仕様変更が発生したとします。
スタミナの回復速度を「10分に1ゲージ」から「5分で1ゲージ」に変更
こんな時、どこを修正すれば良いのでしょうか。
答えは簡単、**「スタミナ管理」**ですね。
このように、スタミナに関する処理を全てスタミナ管理クラスに任せることで、
修正時にとても分かりやすくなります。
以下は、スタミナ管理クラスの例です。
※このコードはあくまでも説明するために簡略化しており、
実在するゲームの管理はもっとすごいことをしていますのでご注意ください。
public class スタミナ管理 {
private int スタミナ;
private long 直近の回復時間;
public int getスタミナ() {
return this.現在のスタミナ;
}
public void 回復チェック() {
// 現在時間をmillSecで取得
long 現在時間 = new Date().getTime();
// 10分 = 10 * 60(sec) * 1000(millSec)
if (現在時間 - 直近の回復時間 > 10 * 60 * 1000) {
// スタミナを1回復
this.現在のスタミナ++;
// 直近の回復時間を現在時間に更新
this.直近の回復時間 = 現在時間;
}
}
}
さて、ここでポイントとなるのが、スタミナの数値に関する部分です。
private int スタミナ;
private int 直近の回復時間;
この、クラスの内部に宣言した変数を「フィールド値」と呼び、クラスの中で管理できる変数を指します。
ここに宣言したフィールド値は、原則としてクラス内であればのどこからでもアクセスできます。
特に、Javaの場合にはこのフィールド値をprivateとし、外部から直接値の変更をさせなくするのが一般的です。
ゲッター(Getter)
さて、このクラスでは、現在のスタミナを取得するために以下のようなメソッドを用意しています。
public int getスタミナ() {
return this.現在のスタミナ;
}
このように、フィード値を取得することを目的としたメソッドを「ゲッター(getter)」と呼びます。
クラスの外部からは、publicとして宣言されたメソッドのみ呼び出すことができます。
ゲッターを用意することで、
- getスタミナ()を呼び出すだけで現在のスタミナを取得できる
- 直接スタミナを書き換えることができなくなる
ということを明示することができます。
このように、クラス自身の持つフィールドに対する操作を意図的に制限することで意図しない動きを抑制することができます。
仕様変更のさらなる仕様変更
さて、ではここで先ほどの仕様変更を思い出しましょう。
スタミナの回復速度を「10分に1ゲージ」から「5分で1ゲージ」に変更
これを実現するためには、以下の部分を修正すれば良いですね。
if (現在時間 - 直近の回復時間 > 10 * 60 * 1000) {
さて、5分に直そうと思ったところ、こんなことを言われました。
基本は10分にして、イベントの時は1分に1ゲージとか5分で1ゲージとかしてくれ
さて、こうなるとただ書き換えるだけでは対応できませんね。
セッター(Setter)
仕方ないので、この回復スパンをフィールド値に変更して、外部から書き換えができるようにしてしまいましょう。
public class スタミナ管理 {
private int スタミナ;
private long 直近の回復時間;
private long 回復スパン;
public int getスタミナ() {
return this.現在のスタミナ;
}
public void set回復スパン(int 回復分数) {
this.回復スパン = 回復分数 * 60 * 1000;
}
public void 回復チェック() {
// 現在時間をmillSecで取得
long 現在時間 = new Date().getTime();
// 10分 = 10 * 60(sec) * 1000(millSec)
if (現在時間 - 直近の回復時間 > 10 * 60 * 1000) {
// スタミナを1回復
this.現在のスタミナ++;
// 直近の回復時間を現在時間に更新
this.直近の回復時間 = 現在時間;
}
}
}
さて、ここで登場するのが以下のメソッドです。
public void set回復スパン(int 回復分数) {
this.回復スパン = 回復分数 * 60 * 1000;
}
もし、イベントなどでスタミナ回復時間を減らす場合には、このメソッドを呼び出すことで回復時間を変更することができます。
このように、フィールド値の書き換えを行うことを目的としたメソッドを「セッター(Setter)」と呼びます。
カプセル化
このように、GetterやSetterを用いてフィールドのアクセスを制限することで、
予期しない動きや呼ばれ方をされることを防止することができます。
これを**「カプセル化」**といいます。
もし「カプセル化の存在しない世界線」だったら・・・?
もしカプセル化が存在しない世界線に生きていた場合、どんなことが起きてしまうのでしょうか。
もし、全てのフィールドが外部から簡単にアクセスできることになりますので、
ゲーム画面の至る場所に以下のソースコードが埋め込まれてしまいます。
while(true) {
// 10分ごとに実行
Thread.sleep(10 * 60 * 1000);
スタミナ管理.スタミナ++;
}
もし、これが多重に動作してしまった場合、いきなりスタミナが2回復したり、10分たっても回復しないこともあります。
これがカプセル化していると・・・?
これがもし、うまくカプセル化していたらどうなるでしょうか。
while(true) {
Thread.sleep(1000);
スタミナ回復.回復チェック();
}
この場合、回復チェックは連続で呼ばれても余計な回復をすることはありません。
このように、スタミナをカプセル化することで、
変なバグも生み出さずに綺麗なプログラムになりますね。
カプセル化の目的
カプセル化は、クラス自身が管理する範疇を明確にし、
より分かりやすい呼び出しを実現することを目的としています。
これは、オブジェクト思考型のプログr魔イングで
例えば、このスタミナ管理クラスの役割は「スタミナを管理すること」ですので、
スタミナの増減に関する挙動は他のクラスに任せたくありません。
これを実現するために、外部からスタミナ情報へ直接のアクセスを禁止し、
**「Hey 管理クラス, 今のスタミナは?」**とだけ聞けば良いようにしています。
カプセル化の基本は「外部からの参照はよりシンプルにすること」です。
もし、処理や状態を隠蔽したとしても、
呼び出し方が複雑だったり、一定の順番で呼ばないとうまく動作しないようなものはあまり良いコードとは言えないでしょう。
さいごに
今回はカプセル化について説明しました。
カプセル化を使いこなすことで、多くのバグを生み出す通称「クソコード」を減らすことができます。
Javaに限らず、オブジェクト思考プログラミングをする上でとても大事なことですので、
きちんと身につけてくださいね。
ちなみに、よくあるGetterやSetterは、値の代入と返却しかしないものがほとんどですので、
今回紹介したSetterの使い方はだいぶ特殊なパターンとも言えます。
実際に、GetterやSetterに処理を入れることを禁止されている場合もありますのでご注意ください。