設定
以下のようなクラス構成を考える。
- RPGに出てくるようなキャラクターとして、剣士と魔法使いを考える
- 共通点として、いずれも名前とエネルギーを持つ
- 剣士特有の能力として、剣で切りかかって戦う動作ができる
- 魔法使い特有の能力として、攻撃魔法を唱える動作ができる
- それぞれの動作を行うと、それに応じたエネルギーが消費される
親クラス~汎化
キャラクターの種類によらず共通するものを、親クラスCharacterClass
としてまとめる。
private
なプロパティーとして、$name
(名前)、$energy
(エネルギー)を持つ- インスタンス生成時に名前を与えて生成し、エネルギーの初期値は職業によらず100
- それぞれの値を得る
public
なゲッターを備えている - エネルギー値を変更する
public
メソッドを備えている - 自身の攻撃の番であることを表示する
public
メソッドを備えている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php // inheritance.php class CharacterClass { private $name; private $energy; public function __construct($name) { $this->name = $name; $this->energy = 100; } public function getName() { return $this->name; } public function getEnergy() { return $this->energy; } public function changeEnergy($delta) { $this->energy += $delta; } public function showTurn() { echo $this->name . "の攻撃:\n"; } } |
継承
CharacterClass
クラスを継承し、剣士(Swordfighter)と魔法使いのクラスを定義する。
- 戦士の動作メソッド
attack()
で剣で切りかかり、エネルギーを15消費する - 魔法使いの動作メソッド
spell()
で攻撃魔法を唱え、エネルギーを10消費する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Swordfighter extends CharacterClass { public function attack() { $this->showTurn(); $this->changeEnergy(-15); echo $this->getName() . "は剣で切りかかった!(Energy:" . $this->getEnergy() . ")\n"; } } class Wizard extends CharacterClass { public function spell() { $this->showTurn(); $this->changeEnergy(-10); echo $this->getName() . "は攻撃魔法を唱えた!(Energy:" . $this->getEnergy() . ")\n"; } } |
剣士としてアーサー、魔法使いとしてハリーという名前でインスタンスを生成し、順番に2回ずつ攻撃させる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$arthur = new Swordfighter('アーサー'); $harry = new Wizard('ハリー'); $arthur->attack(); $harry->spell(); $arthur->attack(); $harry->spell(); // アーサーの攻撃: // アーサーは剣で切りかかった!(Energy:85) // ハリーの攻撃: // ハリーは攻撃魔法を唱えた!(Energy:90) // アーサーの攻撃: // アーサーは剣で切りかかった!(Energy:70) // ハリーの攻撃: // ハリーは攻撃魔法を唱えた!(Energy:80) |
メソッドのオーバーライド
CharacterClass
クラスにshowTurn()
メソッドを定義したが、これと同じ名前のメソッドをSwordfighter
クラスとWizard
クラスに定義する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Swordfighter extends CharacterClass { public function showTurn() { echo '戦士'; parent::showTurn(); } ..... } class Wizard extends CharacterClass { public function showTurn() { echo '魔法使い'; parent::showTurn(); } ..... } |
そして同じように実行すると以下のようになり、それぞれの子クラスで定義したshowTurn()
が呼ばれて剣士・魔法使いと表示され、その後に親クラスのメソッドの表示がされている。
1 2 3 4 5 6 7 8 |
// 剣士アーサーの攻撃: // アーサーは剣で切りかかった!(Energy:85) // 魔法使いハリーの攻撃: // ハリーは攻撃魔法を唱えた!(Energy:90) // 剣士アーサーの攻撃: // アーサーは剣で切りかかった!(Energy:70) // 魔法使いハリーの攻撃: // ハリーは攻撃魔法を唱えた!(Energy:80) |
ここで行ったのがメソッドのオーバーライド。
- 親クラスのメソッドと同じ名前のメソッドを子クラスで定義した場合、子クラスのメソッドが呼ばれる
- 子クラスのメソッドの中で
parent::メソッド名
とすると、親クラスのメソッドが呼ばれる。
実行時のメソッドの探索順は以下の通り。
- 現在のクラスで該当するメソッドを探す
- 無ければ親クラスのメソッドを順次遡って探す
parent::
で呼ばれた場合、親クラスのメソッドを探す
コンストラクター
オーバーライド
上記のコードに更に変更を加えて、初期エネルギー値を戦士が100、魔法使いが80となるようにする。
まずCharacterClass
のコンストラクターを変更して、$energy
についても引数で値をあてるようにする。
1 2 3 4 5 6 7 8 9 10 11 |
class CharacterClass { private $name; private $energy; public function __construct($name, $energy) { $this->name = $name; $this->energy = $energy; } ..... } |
そしてSwordfighter
、Wizard
それぞれのクラスにコンストラクターを追加。インスタンス生成時には名前のみを指定し、それぞれのクラスに応じた初期エネルギー値を親クラスのコンストラクターに渡して、名前とともにセットする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Swordfighter extends CharacterClass { public function __construct($name) { parent::__construct($name, 100); } ..... } class Wizard extends CharacterClass { public function __construct($name) { parent::__construct($name, 80); } ..... } |
実行結果は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$arthur = new Swordfighter('アーサー'); $harry = new Wizard('ハリー'); $arthur->attack(); $harry->spell(); $arthur->attack(); $harry->spell(); 剣士アーサーの攻撃: アーサーは剣で切りかかった!(Energy:85) 魔法使いハリーの攻撃: ハリーは攻撃魔法を唱えた!(Energy:70) 剣士アーサーの攻撃: アーサーは剣で切りかかった!(Energy:70) 魔法使いハリーの攻撃: ハリーは攻撃魔法を唱えた!(Energy:60) |
クラスの継承とコンストラクター/デストラクターについて
- インスタンス生成時、子クラスのコンストラクターが見つからなければ、親クラスのコンストラクターが使われる
- 子クラスのコンストラクターを定義した場合、暗黙では親クラスのコンストラクターは呼ばれない
- から親クラスのコンストラクター/デストラクターを呼び出すときは、
parent::_construct/__destruct
で呼び出す - 通常、子クラスのコンストラクターの中で
parent::constructor()
はコードの最初に書くparent::destructor()
はコードの最後に書く
プロパティーのprivate
宣言について
- 親クラスのプロパティー
$name
と$energy
をprivate
宣言にしているので、継承した子クラスから直接はアクセスできない- このため、
public
なメソッドを準備して、アクセスさせている
- このため、
- プロパティーを
public
宣言すると子クラスから$this->name
のようにアクセスできる- しかし
public
宣言すると、クラス外部で任意に名前やエネルギーの値を変更できてしまう
- しかし
protected
宣言にすれば、継承したクラスから$this->name
のようにアクセスできる- ここではプロパティーの変更を厳格にし、アクセスメソッドを準備しているので
private
にした
- ここではプロパティーの変更を厳格にし、アクセスメソッドを準備しているので