sublcassは親の振る舞いを変えてはいけませんか?
これはLSPの一般的な誤解です。サブクラスは、親タイプに忠実である限り、親の動作を変更できます。
Wikipediaには、LSPに違反することを示唆する長い説明があります。
...サブタイプが満たさなければならない多くの行動条件があります。これらは、契約ごとの方法論に似た用語で詳しく説明されており、契約が継承と相互作用する方法にいくつかの制限があります。
- サブタイプでは前提条件を強化できません。
- サブタイプでは事後条件を弱めることはできません。
- スーパータイプの不変式はサブタイプに保存する必要があります。
- 履歴制約(「履歴ルール」)。オブジェクトは、そのメソッド(カプセル化)によってのみ変更可能と見なされます。サブタイプはスーパータイプには存在しないメソッドを導入する可能性があるため、これらのメソッドの導入により、スーパータイプでは許可されないサブタイプの状態変更が可能になる場合があります。これは履歴制約により禁止されています。それは、リスコフとウィングによって導入された新しい要素でした。この制約の違反は、MutablePointをImmutablePointのサブタイプとして定義することで実証できます。不変ポイントの履歴では、作成後の状態は常に同じであるため、これは履歴制約の違反です。したがって、一般にMutablePointの履歴を含めることはできません。ただし、サブタイプに追加されたフィールドは、スーパータイプメソッドを介して観察できないため、安全に変更できます。
個人的には、単純にこれを覚えている方が簡単だと思います。タイプAのメソッドのパラメーターを見ている場合、サブタイプBを渡すと、驚きが生じますか?その場合、LSPの違反があります。
例外を投げることは驚きですか?あんまり。OrderStateでShipメソッドを呼び出しているか、GrantedまたはShippedであるかを問わず、いつでも発生する可能性があります。だから私はそれを説明しなければなりません、それは本当にLSPの違反ではありません。
そうは言っても、この状況に対処するより良い方法があると思います。C#でこれを書いている場合、インターフェイスを使用し、メソッドを呼び出す前にインターフェイスの実装を確認します。たとえば、現在のOrderStateがIShippableを実装していない場合、Shipメソッドを呼び出さないでください。
しかし、この特定の状況ではStateパターンを使用しません。Stateパターンは、このようなドメインオブジェクトの状態よりもアプリケーションの状態にはるかに適しています。
簡単に言えば、これは状態パターンの不自然な例であり、注文の状態を処理するための特に良い方法ではありません。しかし、それは間違いなくLSPを侵害しません。そして、Stateパターンは、それ自体、確かにそうではありません。