私は最近Liskovの代入原理について読み始めました(LSP私は完全に理解するのに苦労しています「前提条件はサブタイプで強化できない」という制限の意味。この制限は、基本クラスから派生クラスへのダウンキャストの必要性を最小限に抑えるか完全に回避する必要があることを示唆する設計原則と矛盾するように思われます。
つまり、私は Animal
クラスを作成し、動物を派生させる Dog
, Bird
、および Human
。前提条件に対するLSPの制限犬、鳥、または人間が一般的な動物種よりも拘束されるべきでない限り、自然にフィットします。 LSPにこだわると、派生クラスは次のような特別な機能を追加します。 Bird.fly()
または Human.makeTool()
それは共通していません Animal
.
基本クラスには少しばかげた感じがする Animal
すべての可能性のある動物のサブタイプのあらゆる可能性のある機能のための仮想的な方法を持っています、しかしそれがしないならば、私はダウンキャストする必要があるでしょう Animal
アクセスする基礎となるサブタイプへの参照それらのユニークな機能ただし、このような憂慮の必要性は、一般的に、設計不良の原因となっていると考えられます。ウィキペディアでさえ、それが提案されていることを示唆しています。 ダウンキャストは悪い習慣と考えられているというLSPのため.
だから私は何が足りないのですか?
ボーナス質問:のクラス階層をもう一度考えてみましょう Animals
上記の明らかにそれはLSP違反であるでしょう Animal.setWeight(weight)
負でない数のみが必要でしたが、 Human.setWeight(weight)
この前提条件を強化し、1000未満の負ではない数を必要としました。 Human
これは次のようになります。 Human(weight, height, gender)
?コンストラクタがウェイトに制限を課した場合、それはLSP違反になりますか?もしそうなら、派生動物の物理的性質の明確な境界を尊重するために、この階層はどのように再設計されるべきですか?
回答:
回答№1は1LSPはすべて行動サブタイプに関するものです。大ざっぱに言えば、 B
のサブタイプです A
それが常に使用できる場合 A
期待されています。さらに、このような使用法では、予想される動作は変わりません。
だから、LSPアプリケーションを考えると、主なポイントは何の「期待される動作の A
"です。あなたの例では、 Animal
。デザインするのがそれほど簡単ではない Animal
すべての動物に共通のインターフェース
LSPにこだわると、派生クラスは特別な機能を追加します。 といった
Bird.fly()
またはHuman.makeTool()
それは動物に共通ではありません。
かなりありません。 LSPはあなたが取り扱うものとみなします。 Animal
s。まるでダウンキャストすることができないかのように。 Human
, Bird
そして他の動物はどんなメソッド、コンストラクタあるいは何でも持つことができます。 LSPとはまったく関係ありません。彼らはちょうどとして使用されたときに期待どおりに動作するはずです Animal
s。
問題は、そのようなインタフェースが非常に限られているということです。実際には、鳥が飛ぶように型切り替えをしなければならず、人々は便利な道具を作ります。
主流のOOP言語における2つの一般的なアプローチは次のとおりです。
- ダウンキャスト
- ビジターパターン
これでダウンキャストに問題はありませんこれは、ネイティブのバリアント型をサポートしていない言語での型の切り替えを通常行うためです。明示的なダウンキャストを避けるために、インターフェイスの階層を導入するのに多くの時間を費やすことができますが、通常はコードの可読性が低下し、保守が難しくなります。
回答№2の場合は1
プログラミングの多くの側面はトレードオフを伴います。固体の原則はその中にあります。クラスのほとんどすべての派生物やインターフェースの実装に対して同じ方法で実行できるような種類のアクションがあり、実際にはインターフェースの主な目的の一部ではない場合でも、いくつかの特定の派生物や実装では解決できないそのような場合には、「インターフェース分離の原則」は、そのようなアクションが共通インターフェース(*)に含まれていないことを示唆しています。実際のオブジェクトが特定の "特別な"機能を持っているかどうかをチェックし、もしそうであればそれを使用するための型。 IEnumerable<Animal>
含まれているアイテムの数が、実装されているかどうかを確認できるかどうかを知りたい ICollection<Animal>
または非ジェネリック ICollection
[ご了承ください List<Cat>
後者は実装していますが、前者は実装していません。 Count
方法]。このような場合には、渡されたインスタンスがこれらのインタフェースを実装する必要がないため、ダウンキャストには何の問題もありません。
(*) 私見では、 IEnumerable
カウントがわかっているかどうか、それが永遠に同じ項目を含むかどうかなど、シーケンスのプロパティを説明するメソッドを含める必要がありましたが、そうではありません。
次のような場合には、ダウンキャストのもう1つの用途があります。あるグループのオブジェクトが別のグループのオブジェクトと互換性がない場合でも、オブジェクトのグループの集合を持ち、各グループ内の特定のオブジェクトインスタンスが互いに「互換性がある」ことを知っています。たとえば、 MaleCat.MateWith()
メソッドはのインスタンスのみを受け付けることができます FemaleCat
、および FemaleKangaroo.MateWith()
withはのインスタンスのみを受け入れます MaleKangaroo()
しかし、ノアが動物の交尾ペアのコレクションを持つための最も実用的な方法は、動物の種類ごとに MateWith()
を受け入れるメソッド Animal
そして適切な型へのダウンキャスト(そしておそらくまた CanMateWith()
プロパティ)。もし MatingPair
を含んで構築されます FemaleHamster
そして MaleWolf
、を呼び出す試み Breed()
そのペアのメソッドは実行時に失敗しますが、コードは互換性のないメイティングペアの構築を避けます、そのような失敗は決して起こるべきではありません。総称はこの種のダウンキャストの必要性を大幅に減らすことができますが、完全に排除するわけではありません。
ダウンキャストがLSPに違反しているかどうかを判断する際の50,000ドルの問題は、メソッドが渡される可能性があるものすべてに対してその契約を維持するかどうかです。 MateWith()
methodの規約では、特定のインスタンスに対してのみ有効に振る舞うことが保証されていることを明記しています。 Animal
そのために CanMateWith()
trueを返しました。これは、のサブタイプが与えられたときに失敗するという事実です。 Animal
LSP違反ではないでしょう。 一般に、型が使用可能であることが保証されていないオブジェクトをコンパイル時に拒絶することは有用であるが、場合によってはコードは構文的に表現できないある種のオブジェクトインスタンスの型間の関係についての知識を有する。という事実 MatingPair
2つを保持します Animal
正常に繁殖できるインスタンス]。ダウンキャストはしばしばコードの匂いですが、オブジェクトの規約と一致した方法で使用されている場合は問題ありません。