裏メニュー第四週 †アクセス修飾子と抽象クラス、インタフェース †先週分の講座で拾いきれなかったPHPのクラス定義に関する諸トピックについて説明します。 アクセス修飾子 †メンバ変数やメンバ関数には適切なアクセス修飾子を指定するべきです。
不必要にアクセス可能な範囲を広げることは、バグを埋め込むことに繋がり易いです。 <?php class Sample { public $public_var; private $private_var; public function __construct() { $this->public_var = "Hi! I'm a public variable."; $this->private_var = "Hi! I'm a private variable."; } } $sample = new Sample(); echo "{$sample->public_var}\n"; //=> "Hi! I'm a public variable."; echo "{$sample->private_var}\n"; //=> "Fatal error: Cannot access private property Sample::$private_var" ?> 14行目で「Fatal error」すなわち「致命的なエラー」が発生します。 「控え目である」もしくはカプセル化 †参考資料として挙げている「PHP でオブジェクト指向の設計をするための 7 つの良い習慣を身につける」に次のような記述があります。 控え目であるということは、クラスや関数の実装の中で中身が見えないようにすることです。 情報を隠すことは基本的な習慣です。実装の詳細を隠す習慣を身につけない限り、 他のどのような習慣を身につけるのも難しいものです。 情報を隠すことはカプセル化としても知られています。 public フィールドを直接公開することがなぜ不適切なのか、 その理由は数多くありますが、なかでも最も重要な理由は、 実装の中で何かが変更された場合にいかなる方法でも対処しきれなくなることです。 OO の概念を使うと変更を分離することができ、 またいかなる変更の影響も実質的に他には伝染しないことを確実にする上で、 カプセル化が重要な役割を果たします。 伝染性のある変更というのは、最初は些細な変更から始まるものです (例えば 3 つの要素を含む配列を変更して 2 つの要素しか含まない配列にする、など)。 ところが突然、些細な変更であったはずのものが、 その 1 つの変更に対応するために次々にコードを変更する羽目になっていることに気付くのです。 情報を隠すための手始めとして簡単な方法は、 フィールドを private にし、public アクセサーを使ってフィールドを公開する方法です (public アクセサーは家の窓のようなものです)。 つまり壁全体を外に向けて開け広げるのではなく、1 つか 2 つの窓のみを持つようにするのです。 引用した文章をPHPプログラムに落し込むと次のようになります。
getPrivateVar?()でオブジェクトのプライベートな変数の値が返ってくることが確認できましたね。 「家族の一員として扱う」もしくは抽象クラス †再度「PHP でオブジェクト指向の設計をするための 7 つの良い習慣を身につける」から引用します。 私が技術リーダーや技術アーキテクトをしているソフトウェア・チームの人達に対して、 私はよく、OO 言語の最大の敵はコピー・アンド・ペースト操作だと言い聞かせています。 最初から OO 設計がされていない場合に、 あるファイルから別のファイルへコードをコピーすることほど大きな混乱を招くものはありません。 あるクラスから別のクラスへコードをコピーしようと思った時には必ず、いったん立ち止まり、 クラスの階層構造を使うことによって類似の機能や同じ機能を活用できないかを考えてみることです。 設計が適切であれば、ほとんどの場合はコードをコピーする必要がまったくないことに気付くはずです。 先週分の講座でお話したように、クラスを継承することで親クラスと同等の能力を持つ子クラスを作成することができます。 継承されるためだけ(親クラスになるためだけ)に定義されるクラスを抽象クラスと呼びます*2。
8行目でPersonクラスのオブジェクトを作ろうとして「Fatal error」が発生します。 これは抽象クラスとして定義したクラスをインスタンス化しようとしたことが原因です。 「メドゥーサを見ないようにする」もしくはインタフェース †またしても「PHP でオブジェクト指向の設計をするための 7 つの良い習慣を身につける」からの引用です。 私は OO の概念を初めて学んだとき、 インターフェースが本当に役立つものか疑問に思っていました。 すると私の同僚が比喩として、 インターフェースを使わないのはメドゥーサの頭を見るのと同じだという話を紹介してくれました。 メドゥーサは、ギリシャ神話に登場する、髪の毛が蛇にされた女性です。 メドゥーサを直接見た人は誰でも石にされてしまいます。 ペルセウスはメドゥーサを殺しますが、 彼は自分の盾に映る彼女の姿を見ることで彼女に立ち向かうことができ、石にされずにすんだのです。 インターフェースはメドゥーサに立ち向かうための鏡すなわち盾に当たります。 専用の具象実装を使ってしまうと、その実装コードが変更された場合に、 それに合わせて皆さんの作成したコードも変更しなければなりません。 実装を直接使うということは、実質的にクラスを石に変えてしまうことになるため、 多くの選択肢が制限されてしまいます。 インタフェースを使用する例を次に示します(単体では動作しません)。
PersonProvider?インタフェースによりgetPerson()がオーバーライドされることが約束されます。 宿題 †
参考資料 † |