2012年3月24日土曜日

【Java】コンストラクタと継承 with FF5

前回、FF5のジョブをクラスとしてインスタンス化することを書きましたが、
今回はその続きです。

おさらいがてらジョブをクラスとして宣言していきます。
public class ナイト {

 public void たたかう() {
  // 装備している武器で攻撃する
 }
 
 public void ぼうぎょ() {
  // 身を守り、ダメージを軽減する
 }
 
 public void 両手持ち() {
  // 武器を両手で持ち、攻撃力を倍増させる
 }
  ・
  ・
  ・
}
public class シーフ {

 public void たたかう() {
  // 装備している武器で攻撃する
 }
 
 public void ぼうぎょ() {
  // 身を守り、ダメージを軽減する
 }
 
 public void ぬすむ() {
  // モンスターの持つ宝を盗む。失敗することもある
 }
  ・
  ・
  ・
}
public class 黒魔道士 {

 public void たたかう() {
  // 装備している武器で攻撃する
 }
 
 public void ぼうぎょ() {
  // 身を守り、ダメージを軽減する
 }
 
 public void 黒魔法() {
  // 黒魔法を使用する
 }
  ・
  ・
  ・
}
各ジョブを見てもらうと気付くと思いますが、
「たたかう」や「ぼうぎょ」などのメソッドはどのジョブでも使用可能なメソッドで、
その内容も一致しています。
これを大量にあるすべてのジョブに実装していくのは面倒ですね。
それどころか、ゲームの仕様が変わって「たたかう」が「こうげき」に変わったらどうでしょう?
すべてのジョブのすべてのメソッドを修正しないといけません。

そこでJavaには継承という機能が用意されています。
各ジョブの共通部分をまとめた"ジョブ"というクラスを作ってみます。

public class ジョブ {

 public int lv; // キャラクターのレベル
 public int hp; // キャラクターのヒットポイント
 public int mp; // キャラクターのマジックポイント

 public ジョブ() {
  // 初期値としてlv=1,hp=50,mp=5を設定し、引数付きコンストラクタを実行
  this(1, 50, 5);
 }

 public ジョブ(int lv, int hp, int mp) {
  this.lv = lv;
  this.hp = hp;
  this.mp = mp;
 }

 public void たたかう() {
  // 装備している武器で攻撃する
 }
 
 public void ぼうぎょ() {
  // 身を守り、ダメージを軽減する
 }
 
 public void チェンジ() {
  // 隊列を変更する
 }

 public void アイテム() {
  // アイテムを使用する
 }
}
見慣れない部分があると思います。
まず3~5行目の変数はメンバ変数(クラス変数)といい、
クラス内のどのメソッド、コンストラクタでも共通で使用できる変数です。
この例ではRPGに必須のレベルやHPをメンバ変数にしてみました。

次に7行目と12行目から始まるメソッド。これがコンストラクタです。
コンストラクタはクラスをインスタンス化(newする)際に呼ばれるメソッドで、
フィールドの初期化などを主に行います。

コンストラクタのメソッド名はクラス名とし、戻り値はなし(型も記述しない)です。

[修飾子] クラス名 (引数,・・・) { }

コンストラクタは複数設定可能で、7行目は引数なしのコンストラクタ。
12行目は引数付きのコンストラクタです。
実際にインスタンス化すると次のような感じです。

public class TestFF5 {

 public static void main (String[] args){
  
  // LV.1、HP50、MP5のジョブクラスのインスタンスsirocoが生成されます
  ジョブ siroco = new ジョブ();
  
  // LV.20、HP500、MP200のジョブクラスのインスタンスkururuが生成されます
  ジョブ kururu = new ジョブ(20, 500, 200);
 }
}
イメージが湧いたでしょうか??
因みにジョブクラスの引数なしコンストラクタのところで
"this(1, 50, 5)"とありますが、thisというのは自らのことで、
ここでは引数付きのコンストラクタを示します。
つまり"ジョブ(1, 50, 5)"と同意です。

また、引数付きのコンストラクタの中で"this.lv"などここでもthisを使用していますが、
これはコンストラクタの引数のlvと区別するために付けています。
(クラスのフィールドは本来はすべて"this."をつけれるが、不要な時は省略できます)


さて、ここからが本題。
各ジョブの共通部分をまとめたジョブクラスが出来たので、
それを継承して各クラスを作ってみましょう。
public class ナイト extends ジョブ {

 public ナイト() {
  super.hp = super.hp * 1.2;
  super.mp = super.mp * 0.6;
 }

 public void 両手持ち() {
  // 武器を両手で持ち、攻撃力を倍増させる
 }
  ・
  ・
  ・
}
public class シーフ extends ジョブ {

 public void ぬすむ() {
  // モンスターの持つ宝を盗む。失敗することもある
 }
  ・
  ・
  ・
}
public class 黒魔道士 extends ジョブ {

 public void 黒魔法() {
  // 黒魔法を使用する
 }
  ・
  ・
  ・
}
public class TestFF5 {

 public static void main (String[] args){
  
  // シーフのインスタンスsirocoが生成されます。
  // このとき、スーパークラスであるジョブクラスの引数なしコンストラクタが実行され、
  // 各メンバ変数ははLV.1、HP50、MP5となる
  シーフ siroco = new シーフ();

  // スーパークラスであるジョブクラスのたたかうメソッドが実行される
  siroco.たたかう();
  
  // 黒魔道士クラスのインスタンスkururuが生成されます。
  // このとき、スーパークラスであるジョブクラスの引数付きコンストラクタが実行され、
  // 各メンバ変数はLV.20、HP500、MP200となる
  黒魔道士 kururu = new 黒魔道士(20, 500, 200);

  // 黒魔道士クラスの黒魔法メソッドが実行される
  kururu.黒魔法();

  // ナイトクラスのインスタンスrenaが生成されます。
  // このとき、スーパークラスであるジョブクラスの引数なしコンストラクタが
  // 実行され、メンバ変数はLV.1、HP50、MP5となります。
  // そのあとナイトクラスのコンストラクタが実行され、
  // ジョブクラスのhpを1.2倍、mpを0.6倍にして代入しています。
  // つまりrenaの状態はLV.1、HP60、MP6となります
  ナイト rena = new ナイト();
 }
}
クラスを継承するには、クラス宣言の際に"extends 継承するクラス名"で行います。
継承したクラス(子クラス)は継承元のクラス(親クラス、スーパークラス)の
フィールドを使用できます。

これでどのジョブからでも「たたかう」や「ぼうぎょ」を使う事が出来ます。
また、共通部分の変更が発生しても、スーパークラスであるジョブクラスを
修正するだけで、すべてのクラスで修正後のメソッドを使用できます。

上の例でナイトクラスにだけコンストラクタを設定しています。
他のジョブについては記述していませんが、この場合は引数なしの
デフォルトコンストラクタが暗黙に宣言されています。


因みに、ナイトクラスのコンストラクタ内でスーパークラスのメンバ変数に
直接あたいを代入しているが、これはあまりよろしくありません。
メンバ変数をprivate宣言し、アクセッサメソッド(ゲッター・フッター)を使うほうが良いです。
今回は説明前なので、あえて直接代入とさせています。(そのほうがわかりやすいと思って)


インスタンス化の際のコンストラクタの実行順としては、
まずスーパークラスのコンストラクタが実行され、
そのあと子クラスのコンストラクタが実行されます。


長々と書いてきましたが、継承について少しは感じがつかめたでしょうか?
細かいことを書けば、大量の説明が必要になるので、書きませんが、
詳しく知りたくなった人は古本屋さんとかでJavaの本を手にとって見てはいかがでしょう??

次回は条件文について書こうと思います。
(ここは流石にFF5は無理かな~)

0 件のコメント:

コメントを投稿