実装の話が続きましたので、少し「設計の仕方」についても学んでいきましょう。オブジェクト指向の「継承」は、おおよそ「再利用性」と「拡張性」の文脈で語られることが多いので、その辺りを念頭に置いておくとよいでしょう。
クラス定義の設計において、特に継承を考える場合「is-a関係」について語られることが多いので、「is-a関係」について先に簡単に説明しておきます。
is-a関係は「B is a A」となり「BはA(の一種)である」という感じで用いられます。
例えば「パイナップル is a フルーツ」で、つまり「パイナップルはフルーツ(の一種)である」が、これは成り立ちます。一方で例えば「ショートケーキ is a フルーツ」は、これは「ショートケーキはフルーツ(の一種)である」となり、これは成り立たない関係です。
このような「is-a関係」が「成り立つ」ようなクラス間であれば、今回の「継承」を使うとよい、と考えられています。継承を設計するときは、この「is-a関係」を意識しながら行うとよいでしょう。
なお、is-a関係と併せて「has-a関係」というものが出てきますが、これは次々回に予定している、「trait」という機能の解説時に説明します。
理論背景は以上です。ここからは実際に「継承込みのクラス設計」をしてみます。比較的イメージしやすいと思われる「ECサイトで扱う商品」を例題としてみましょう。
手始めに「書籍を扱う」と想定して、商品のクラスを簡単に、疑似コードで書いてみましょう。セッターとゲッターは省略しています。
class 書籍 { function __construct(商品ID) { 商品IDをkeyにDBから情報を取り出してプロパティに入れる処理; } // $書籍名; $ISBN; $出版社名; $著者名; // 配列 $金額; $簡単な説明/紹介文; }
ビジネスが展開していって、扱う商品に「家電が増えた」とします。普通に考えると、家電用のクラスを別途作成します。
class 家電 { function __construct(商品ID) { 商品IDをkeyにDBから情報を取り出してプロパティに入れる処理; } // $家電名; $メーカー名; $消費電力; $金額; $簡単な説明/紹介文; }
まずコンストラクターが非常に似ています。実装すると、おそらく「テーブル名」などわずかな差異はあると考えられますが、ほとんど、書籍クラスと変わらないロジックになるでしょう。
「家電名」と「書籍名」はどちらも「商品名」と言ってしまえば一緒ですし、「出版社名」と「メーカー名」も「発売元名」と考えると一緒です。「金額」「簡単な説明/紹介文」に至っては完全に同一です。その一方で、「ISBN」や「著者名」は書籍にしかありませんし、「消費電力」は家電にしかありません。
ここで考えてみたいのが「これらはis-a関係になり得るか?」ということです。書籍と家電はどちらもなり得ませんが、「書籍 is a 商品」「家電 is a 商品」として考えると、どちらも「商品(の一種)」になります。こういうケースで、継承を使うとよいでしょう。
継承元クラスが「商品」となり、継承先クラスが「書籍」「家電」になります。なお、継承元クラスを「親クラス」、継承先クラスを「子クラス」という言い方をすることがあるので、覚えておくとよいでしょう。
では実際に親クラスを切り出してみます。なお「家電名と書籍名は、商品名に改名」「出版社名とメーカー名は、発売元名に改名」します。
class 商品 { function __construct(商品ID) { 商品IDをkeyにDBから情報を取り出してプロパティに入れる処理のうち、全商品に共通の部分; } $商品名; $発売元名; $金額; $簡単な説明/紹介文; } // class 書籍 extends 商品 { function __construct(商品ID) { 商品IDをkeyにDBから情報を取り出してプロパティに入れる処理のうち、書籍に固有の部分; } // $ISBN; $著者名; // 配列 } // class 家電 extends 商品 { function __construct(商品ID) { 商品IDをkeyにDBから情報を取り出してプロパティに入れる処理のうち、家電に固有の部分; } // $消費電力; }
全体としてスッキリしました。また、このようにクラスを分けておけば、今後「全商品に共通の変更や追加」があれば、それは商品クラスに対して改修を行えば、クラス1つの改修で「全てのクラスが適切に改修される」ようになります。逆に、「書籍にのみ適用したい変更や追加」があれば、書籍クラスに改修を行うことで「他のクラスに影響を及ぼさない改修」が可能になります。
現場で多く見掛ける困り事の一つが「クラス設計をしているが、親クラスを適切に切り出せない」というものです。無論「すぐに見当が付く」設計であれば、それを実装すればよいのですが、そうではない場合は無理に親クラスを作ろうとしない方が実践的で現実的です。
まずは「必要なクラスをクラス1つで実装」し、「後で、必要になってから親クラスを作成」する方が、実践的です。
オブジェクト指向において、多くの人が悩むのは「実装の仕方」ではなく「親クラスの切り方」ですし、一部で「オブジェクト指向が使えない/使いにくい」と言われている理由の多くも「親クラスの切り方で失敗している」ケースです。
オブジェクト指向を学ぶときは、もちろん「実装の仕方」も重要ですが、同時に「これは、どんなときに使うのか」についても、その利点を意識しながら学ぶと習得が早くなるので、少しだけ実務も意識してもらえればと思います。
さて今回までは、どちらかというと「どのプログラミング言語でも共通である、あるいは、少なくとも通用する」内容を解説してきました。次回は、「PHPに固有(に近い)」仕様である、「マジックメソッド」について説明します。
Copyright © ITmedia, Inc. All Rights Reserved.