私は、リスコフ代替原理の違反の可能性について、この非常に投票された質問に従っていました。Liskov Substitutionの原則が何であるかは知っていますが、開発者としてオブジェクト指向コードを書いている間にこの原則を考えないと、何が間違っているのかが私の心ではまだはっきりしていません。
私は、リスコフ代替原理の違反の可能性について、この非常に投票された質問に従っていました。Liskov Substitutionの原則が何であるかは知っていますが、開発者としてオブジェクト指向コードを書いている間にこの原則を考えないと、何が間違っているのかが私の心ではまだはっきりしていません。
回答:
私はそれが非常によく投票された理由の一つであるその質問で非常によく述べられていると思う
タスクでClose()を呼び出すと、開始されたステータスのProjectTaskの場合は呼び出しが失敗する可能性がありますが、ベースタスクの場合は失敗しません。
想像してみてください:
public void ProcessTaskAndClose(Task taskToProcess)
{
taskToProcess.Execute();
taskToProcess.DateProcessed = DateTime.Now;
taskToProcess.Close();
}
このメソッドでは、.Close()呼び出しがときどき爆発するため、派生型の具体的な実装に基づいて、Taskにサブタイプがない場合のこのメソッドの記述方法からこのメソッドの動作を変更する必要がありますこのメソッドに渡されました。
liskov置換違反のため、型を使用するコードは、派生型の内部動作を明示的に認識して、それらを異なる方法で処理する必要があります。これにより、コードが緊密に結合され、一般的に実装を一貫して使用することが難しくなります。
基本クラスで定義されたコントラクトを満たさない場合、結果がオフになると黙って失敗する可能性があります。
これらのいずれかが保持されない場合、発信者は予期しない結果を得る可能性があります。
インタビューの質問の年代記から古典的な例を考えてみましょう。あなたは楕円から円を導き出しました。どうして?もちろん、円はIS-AN楕円だからです!
を除く...楕円には2つの機能があります。
Ellipse.set_alpha_radius(d)
Ellipse.set_beta_radius(d)
サークルの半径は均一なので、明らかに、これらはサークルに対して再定義する必要があります。次の2つの可能性があります。
ほとんどのOO言語は2番目の言語をサポートしていませんが、それには十分な理由があります。CircleがCircleでなくなったことは驚くべきことです。したがって、最初のオプションが最適です。ただし、次の機能を検討してください。
some_function(Ellipse byref e)
some_functionがe.set_alpha_radiusを呼び出すと想像してください。しかし、eは実際には円であったため、驚くべきことにベータ半径も設定されています。
そして、ここに代替原理があります。サブクラスはスーパークラスの代替である必要があります。そうでなければ、驚くべきことが起こります。
素人の言葉で:
コードには非常に多くのCASE / switch句があります。
これらのCASE / switch句のすべてに、時々追加される新しいケースが必要になります。つまり、コードベースは、本来あるべきほどスケーラブルで保守可能ではありません。
LSPを使用すると、コードをハードウェアのように機能させることができます。
新しい外部スピーカーのペアを購入したため、iPodを変更する必要はありません。古い外部スピーカーと新しい外部スピーカーの両方が同じインターフェースを尊重しているため、iPodは必要な機能を失うことなく交換できます。
typeof(someObject)
「許可」することを決定するために切り替えを参照している場合は、確かですが、それは完全に別のアンチパターンです。
javaのUndoManagerを使用して実際の例を示す
AbstractUndoableEdit
2つの状態(元に戻すおよびやり直し)があることを指定するコントラクトから継承し、1つの呼び出しundo()
とredo()
ただし、UndoManagerにはより多くの状態があり、元に戻すバッファーのように動作します(すべての編集ではなくundo
一部の呼び出しを元に戻し、事後条件を弱めます)
これは、UndoManagerをCompoundEditに追加してから呼び出すと、CompoundEditでend()
undoを呼び出すとundo()
、編集が部分的に取り消されたままになると、各編集で呼び出すという仮想的な状況になります。
それUndoManager
を避けるために自分でロールバックしました(おそらく名前を変更する必要がUndoBuffer
あります)
例:UIフレームワークを使用しており、Control
基本クラスをサブクラス化して独自のカスタムUIコントロールを作成します。Control
基本クラスは、メソッド定義されなければならない、ネストされたコントロールのコレクションを(もしあれば)を返すを。しかし、メソッドをオーバーライドして、実際にアメリカ合衆国大統領の誕生日のリストを返すようにします。getSubControls()
これで何がうまくいかないのでしょうか?コントロールのリストが期待どおりに返されないため、コントロールのレンダリングが失敗することは明らかです。ほとんどの場合、UIがクラッシュします。あなたはされている契約破りコントロールのサブクラスをを遵守することが期待されます。
モデリングの観点から見ることもできます。クラスのインスタンスがクラスのインスタンスでA
もあると言うときB
、「クラスのインスタンスの観測可能な動作は、クラスのインスタンスのA
観測可能な動作としても分類できる」ことを意味しますB
(これは、クラスB
がクラスA
。)
したがって、LSPに違反するということは、設計に矛盾があることを意味します。オブジェクトに対していくつかのカテゴリを定義しているのに、実装でそれらを尊重していないのであれば、何かが間違っているに違いありません。
「このボックスには青いボールのみが含まれています」というタグを付けてボックスを作成し、そこに赤いボールを投げるようなものです。間違った情報を表示する場合、そのようなタグの使用は何ですか?
最近、いくつかの主要なLiskov違反者を含むコードベースを継承しました。重要なクラスで。これは私に大きな痛みをもたらしました。理由を説明させてください。
私が持っているClass A
、それはから派生していClass B
ます。 Class A
そして、Class B
プロパティの束共有するClass A
独自の実装でオーバーライドします。設定または取得Class A
財産ことから、正確に同じプロパティを設定または取得に異なる効果がありますClass B
。
public Class A
{
public virtual string Name
{
get; set;
}
}
Class B : A
{
public override string Name
{
get
{
return TranslateName(base.Name);
}
set
{
base.Name = value;
FunctionWithSideEffects();
}
}
}
これが.NETで翻訳を行うためのまったくひどい方法であるという事実を別にすれば、このコードには他にも多くの問題があります。
この場合Name
、多くの場所でインデックスおよびフロー制御変数として使用されます。上記のクラスは、生の形式と派生した形式の両方でコードベース全体に散らばっています。この場合、リスコフ置換の原則に違反するということは、基本クラスをとる各関数へのすべての呼び出しのコンテキストを知る必要があるということです。
コード用途の両方のオブジェクトClass A
とClass B
私は単純に作ることができないので、Class A
使用に人々を強制的に抽象的なClass B
。
動作するいくつかの非常に有用なユーティリティ関数と、動作するClass A
他の非常に有用なユーティリティ関数がありますClass B
。理想的には私が上で動作することができます任意のユーティリティ関数を使用できるようにしたいと思いClass A
上をClass B
。aをとる関数の多くは、LSP違反ではない場合Class B
、簡単にa をとることができますClass A
。
これについて最悪なのは、アプリケーション全体がこれら2つのクラスに依存し、常に両方のクラスで動作し、これを変更すると100の方法で壊れるので、この特定のケースはリファクタリングが本当に難しいことです(これを行うつもりですとにかく)。
これを修正するには、NameTranslated
プロパティを作成する必要があります。これは、プロパティのClass B
バージョンになりName
、派生Name
プロパティへのすべての参照を慎重に変更して、新しいNameTranslated
プロパティを使用します。ただし、これらの参照の1つでも間違っていると、アプリケーション全体が爆破する可能性があります。
コードベースには単体テストがないため、これは開発者が直面する可能性のある最も危険なシナリオに近いものです。違反を変更しないと、各メソッドでどのタイプのオブジェクトが操作されているかを追跡するために膨大な精神エネルギーを費やす必要があり、違反を修正すると、不適切な時間に製品全体が爆発する可能性があります。
BaseName
を作成しTranslatedName
、クラスAスタイルName
とクラスBの両方の意味にアクセスするとどうなりますか?その後Name
、型の変数にアクセスしようとするB
と、コンパイラエラーで拒否されるため、すべての参照が他の形式のいずれかに変換されたことを確認できます。