
SOLID原則の中でも最もイメージしづらいとされるLSPですが、実は「オープン・クローズドの原則(拡張に対して開き、修正に対して閉じる)」を守る上で、オブジェクト指向において非常に重要な概念です。
1. LSPの核心は「振る舞いの契約」
LSPを一言で表すと「サブタイプ(子)はスーパータイプ(親)の振る舞いの約束を破ってはならない」という契約です。 静的型付け言語のコンパイラは「型」が合っているかは見てくれますが、「振る舞い」が守られているかまではチェックしてくれません。そのため、これはコンパイラではなく人間が意識して守るべき契約となります。
2. 陥りがちなアンチパターンと契約違反
スライドにもある「正方形と長方形」の例のように、正方形を長方形の仲間として継承させようとすると、縦横のサイズ制約を満たすために呼び出し側で if-else の分岐が増えてしまい、結果的にオープン・クローズドの原則に反してしまいます。
LSPにおける契約違反(アンチパターン)には以下の要素があります。 事前条件を厳しくしてしまう: 親よりも厳しい入力ルールを子が設けてしまうと、親のつもりで呼び出したクライアントが門前払いされてしまいます。 事後条件を弱めてしまう: 親が保証した出力の約束を子が破ってしまうと、アプリ自体はクラッシュしなくても、裏で密かに計算が狂い続けるなどの予期せぬエラーを引き起こします。 その他のアンチパターン: 「飛べない鳥」のように、親のメソッドをオーバーライドした際に例外をスローしたり、メソッドを未実装のままにしたりする行為もLSP違反となります。
3. 現代の開発における結論:継承の難しさとコンポジションへの移行
LSPを守るための解決策として「共通部分を括り出す」という手法がありますが、そもそも継承自体が非常に難しい概念です。 継承は密結合を引き起こしやすく、親の修正が子を壊す危険性を孕んでいます。さらに、ドメイン固有の振る舞いの違い(例えば「見込み客」と「潜在顧客」の違いなど)は、人間が制御するのも難しく、AIにも理解しづらい領域です。
そのため、現代の開発においては継承の代わりにコンポジション(委譲)が多用される傾向にあります。(※関数型プログラミングのようにクラスや継承の概念が存在しない言語では、そもそもLSPを意識する必要がありません。)
「振る舞いの約束を守るのが難しいのであれば、安易な継承は避けるべき」というのが、AI時代のドメイン設計も踏まえた現代的な結論と言えます。
参考文献