前置き
オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方を読み、咀嚼のために PHPで同じようなことを実装して理解を深めます。助言いただけると幸いです。
概要
オブジェクト指向の考え方として、依存しすぎない(=疎結合)であることが良しとされる。
そしてこの考えは継承関係にあるクラス同士でも適用される。
- 継承関係にあるクラス同士がどのように依存しているか
- その依存をどうやって解消するか
をPHPのコードで例を示します。
依存しすぎている継承関係
以下の例は、幾つかの子クラスで共通した処理を行う場合を仮定しています。
その共通処理を親クラスのメソッドとしてまとめることで、Don't Repeat Yourselfに則ろうとしています。
<?php
class ParentModel
{
public function initialize()
{
// 親クラスで実装している共通処理
echo "parent initialize done...\n";
}
}
<?php
require __DIR__."/ParentModel.php";
class ChildModel extends ParentModel
{
public function initialize()
{
// 親クラスにまとめられた共通処理を呼ぶ
parent::initialize();
// 子クラス独自の処理を行う
echo "child initialize done...\n";
}
}
<?php
require __DIR__."/ChildModel.php";
// このプログラムを実行する
$child = new ChildModel();
$child->initialize();
parent initialize done...
child initialize done...
非常にシンプルなコードですが、この継承関係にある2つのクラスで注目すべき依存とはparent::initialize()
です。
ここで問題なのは子クラスが「親クラスがinitialize()を実装している」ことを知っているという点です。
(ここでの「知っている」とはソースコードに明示的に記述されていることを意味します。)
あるクラスが他のクラスについて知っているというのは依存している事を示しています。新たなサブクラスを作成しようとした別のプログラマがparent::initialize()
を書き忘れた途端、予期せぬ挙動が起きるかもしれません。
依存を解消する
依存を解消するには、子クラスのChildModelからparent::initialize()
という行が存在しなければ解決です。
ということで、以下のようにソースコードを変更します。
<?php
class ParentModel
{
public function initialize()
{
// 親クラスで実装している共通処理
echo "parent initialize done...\n";
// 独自処理を呼ぶ
$this->postInitialize();
}
protected function postInitialize()
{
// 親クラスで実装するが特に何もしない
}
}
<?php
require __DIR__."/ParentModel.php";
class ChildModel extends ParentModel
{
protected function postInitialize()
{
// 子クラス独自の処理を行う
echo "child initialize done...\n";
}
}
call.phpは変更ありません。
この変更のポイントはcall.phpから見ると
- 変更前:子クラスに実装されたメソッドを呼ぶ
- 変更後:親クラスに実装されたメソッドを呼ぶ(間接的に子メソッド呼ぶ)
に変わった点です。
この変更によりChildModel.phpでは親クラスのメソッドを明示的に呼ぶ必要がなくなったので依存が解消されました。
所感
空のメソッドがあるというのは少しモヤっとするのですが、こんなもんだと割り切るんですかね?あまり見たことがないのでかなり拒絶されそうな気がしますが・・・
abstractクラスにしてpostInitialize()を抽象化すると状況が多少改善されるのでしょうか。
ただしその場合は、子クラス独自の処理がないとき子クラス側に何もしないメソッドが追加されることになりますね。
(よくよく考えればcakephpのコアソースにも空のメソッドがどこかにあったかも・・・。知らないだけで一般的?)