すでにご存知かと思いますが、オブジェクト指向にはいくつかの原則があります。
それらは、先人達がオブジェクト指向を研究したり実践したりしていく中で、発見されてきたものです。どれも、オブジェクト指向を正しく利用するためには非常に重要なことですので、今後順次解説していきます。
今回は、リスコフの置換原則のお話です。
英語で言うと "the Liskov Substitution Principle" ということで、LSP と略されたりします。
コンテンツ
リスコフの置換原則…と聞くとまず思うのが、「リスコフ」ってなに?ということだと思います。はい、「リスコフ」というのはこの原則を提唱した人の名前です。
「そういう名前の人が言い出したんだな」とだけ思っとけば OK です。
そうすると、肝心なのは「置換原則」ってとこだけですね。
じゃあ、何と何を置換するでしょう?
はい、スーパークラスを、サブクラスで置き換えるのです。
ざっくり言っちゃうと、
スーパークラスとサブクラスを定義するとき、サブクラスはスーパークラスを置き換えることができなければならない。
ということが、この原則なのです。
たとえれば職能
ソフトウェア開発者と、事務担当です。
今、必要な事務仕事が一つあるのですが、事務担当者は全員手が塞がっています。
さて、あなたは少しいいかげんに、「事務」というのは会社員の基本形で、開発者はそれにソフトウェア開発の技術を付け足したものだと考えました。
つまり、漠然と事務担当は開発者のスーパークラスだと思ったわけです。
そこで、開発者に事務仕事を頼みました。
さて、結果は?
もちろん、惨憺たるものですよね。
ばかみたいに時間がかかった上に、間違いだらけ。
あなたは、この開発者を叱るべきですか?
もちろん、違います。
間違いを犯したのは開発者ではなく、あなたなのですから。
ソフトウェア開発者と事務担当は、置き換えできないのです。
これは、ソフトウェア開発者と事務担当には、どちらがどちらのスーパークラスということはなく、直接関係のない職業だということを示しています。
あなたの羅針盤
あなたのオブジェクト指向の羅針盤の一つとして、ぜひ覚えておいてください。
実際のはなし
まず、クラス A のインスタンスがクラス B のインスタンスに依存しているとします。
ここで、クラス B のサブクラス B' を作成しました。
このときクラス A に対して、クラス B の代わりに B' を渡しても、クラス A は正常に動作しなければなりません。
そのためには、どうしたらいいのでしょう?
まず、クラスを作成するときに、サブクラスでオーバーライドされた結果、動作が意図しないものに変わってしまう可能性があるようなメンバを公開してはならないということです。
公開する必要がなければ、private にするべきです。
あるいは、Java のような final 修飾子をもった言語であれば、それを使うという選択肢もあります。
また、継承を前提としていないクラスの場合は、クラス宣言自体に final を付けるべきです。
そして、サブクラスを作るときには、結果がスーパークラスの意図から外れてしまうようなオーバーライドをしてはいけません。
上記のように、スーパークラスがきちんと作ってあれば、これはそれほど難しい問題ではないはずです。
こうなっていれば、クラス A は実際にクラス B だろうが B' だろうが B'' だろうが、クラス B かそのサブクラスが渡されたのであれば、常にうまく動作するはずです。
クラス A もこれにのっとって作ってあれば、それに依存するクラスもうまくいく。
そのクラスもちゃんと作ってあれば、それに依存するクラスもまた…。
というかんじで、実はこんな細かいことが、アプリケーション全体の変更に対する強さに大きく影響するわけです。
契約
「動作が意図しないものに変わってしまう可能性があるような…」
と
「結果がスーパークラスの意図から外れてしまうような…」
というところです。
ここで言う意図は、契約に基づく設計 (Design by Contract) の事前条件と事後条件のことでもあります。
上記の説明を契約っぽく言い直してみると、
- サブクラスはスーパークラスの事前条件を厳しくしてはならない
- サブクラスはスーパークラスの事後条件を緩めてはならない
シンプルに言えば、サブクラスは、スーパークラスが動く状況なら必ず動かなければならないし、スーパークラスが達成することは必ず達成しなければならないということです。
これが、クライアントから見て置き換え可能ということなのです。
犯罪者
リスコフの置換原則が守れないと、クライアントのクラスには依存先のクラスごとに条件文 (if 文とか) が必要になります。
この条件文が諸悪の根源となり、アプリケーションをガチガチの一枚岩に固めてしまいます。
たとえば、前述の例と同じようにクラス A がクラス B に依存しているとします。
次にクラス B のサブクラス B' を作りました。
でも、B' の挙動は B とは互換性がありません。
ということになると、クラス A は、相手が B なのか B' なのかを判定して処理を行わなければなりませんね。
この判定に、条件文が必要になるわけです。
さて、また新たに B のサブクラス B'' を作りました。
もちろん、互換性はなし。
クラス A は、この影響をもろにうけて、変更を余儀なくされます。
条件文に、新たな条件を追加しなければならないからです。
このような条件文がアプリケーションのあちこちにあったとしたら、どうでしょうか?
なにかひとつ追加したり変更したりするたびに、たくさんの変更が必要なため、大きなコストがかかるようになります。
これが、オープン・クローズドの原則を守らないことによる代償です。
このように、リスコフの置換原則を破ると、破ったクラスそのものよりも、そのクラスを使うクライアント側のクラスがオープン・クローズドの原則を守れなくなるのです。
こうなると、リスコフの置換原則を破ったクラスはまるで加害者のようですね。
そうです。リスコフの置換原則を破るクラスは、犯罪者クラスなのです。
といっても、この原則を 100% 守ることはなかなか難しいのですが。
ポリモーフィズムの羅針盤
メッセージの受け手のオブジェクトが変わると、オブジェクトによって異なる動作をするのがポリモーフィズムでしたよね。
このとき、いくら違う動作をしてくれるといっても、クライアントからは同じ動作をしているように見えなければなりません。
ポリモーフィズムとリスコフの置換原則をつなげて大雑把に言っちゃうと、
「同じインターフェイスを持っていれば、違うオブジェクトでも同じように扱える。
このとき、オブジェクトによって動作は異なる。
ただし、クライアントからは同じ動作をしているように見えなければならない。」
ということになります。
違うことをしていてもクライアントから同じように見えれば、いろんなことを同じやり方で扱えて、しかもうまくいく。
と、そういうわけなんです。
オススメ
今回お話しした、リスコフの置換原則を含めて、5つの原則がまとめてわかりやすく説明されてます。
オブジェクト指向の原則以外にも、デザインパターンの実戦的な解説やXP(エクストリームプログラミング)プラクティスなど、チームでのソフトウェア開発に有益な話が満載です。
2013.02.22
説明をわかりやすくするために、改訂しました。
今まで、クラス宣言やメソッドの修飾子にfinalを付けるべきかの判断を怠っていたように思います。
返信削除その他etc、とても勉強になりました。
そして、【アジャイルソフトウェア開発の奥義】が欲しくなりました。
それにしても、コンピュータ書籍って値段高いですよね。( ̄_ ̄ i)
サンドラさん、はじめてのコメント、ありがとうございます。
返信削除これからもよろしくお願いします。
そうですねー、逆方向に怠っちゃえばいいのかもしれません。
とりあえずは、なんでもかんでもfinalってことで。
とくに、クラスにfinal付けちゃえば、メソッドはもういちいちfinal付けなくても、暗黙的に全部finalになっちゃうので、らくちんです。
コンピュータ書籍、特に専門的な技術書は高いですよねー。
私も今までに技術書に費やしたお金を考えると、目もくらむばかりです。
まぁ、全てはおもしろい仕事をして、それに見合った報酬を頂くための、自分への投資だと割り切ってます。
でないと、なかなか…。