フォールバックのある特別なケースは、リスコフ代替原理に違反しますか?


20

FooInterface次のシグネチャを持つインターフェイスがあるとします。

interface FooInterface {
    public function doSomething(SomethingInterface something);
}

そして、ConcreteFooそのインターフェースを実装する具体的なクラス:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
    }

}

ConcreteFoo::doSomething()特別なタイプのSomethingInterfaceオブジェクトが渡された場合、ユニークな何かをしたいと思います(と呼ばれますSpecialSomething)。

メソッドの前提条件を強化するか、新しい例外をスローする場合、それは間違いなくLSP違反ですが、SpecialSomething汎用SomethingInterfaceオブジェクトのフォールバックを提供しながらオブジェクトを特別にケースに入れた場合でもLSP違反になりますか?何かのようなもの:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
        if (something instanceof SpecialSomething) {
            // Do SpecialSomething magic
        }
        else {
            // Do generic SomethingInterface magic
        }
    }

}

回答:


19

それは可能性があるため、契約である他に何に依存するLSPを侵害することdoSomethingの方法。しかし、LSPに違反していなくても、ほぼ間違いなくコードの匂いです。

例えば、契約の一部があればdoSomething、それが呼び出すことでsomething.commitUpdates()復帰する前に、少なくとも一度、およびそれが呼び出す特別な場合のためにcommitSpecialUpdates()代わり、そのがある LSPの違反。場合であってもSpecialSomethingcommitSpecialUpdates()方法を意識的に同じもののすべてを行うために設計されたcommitUpdates()だけで先制LSP違反の周りハッキングだと、もう1つは一貫LSPに従った場合、正確に牛車1のソートは行う必要はありませんでした。このようなことがあなたのケースに当てはまるかどうかは、そのメソッドの契約を(明示的または暗黙的に)確認することで把握する必要があります。

これがコードのにおいである理由は、引数のいずれかの具体的な型をチェックすると、そもそもそのためのインターフェース/抽象型を定義するポイントを見逃し、原則としてメソッドが機能することさえ保証できないためです(想像してください)誰かが呼び出されるSpecialSomethingという仮定でサブクラスを記述した場合commitUpdates())。まず、これらの特別な更新を既存のSomethingInterface; それが最良の結果です。それができないと本当に確信している場合は、インターフェースを更新する必要があります。インターフェイスを制御しない場合は、必要な処理を行う独自のインターフェイスの作成を検討する必要があります。すべてのインターフェイスに対応できるインターフェイスを思い付かない場合は、インターフェイスを完全に破棄して、さまざまな具体的なタイプを使用する複数のメソッドを用意するか、さらに大きなリファクタリングを行う必要があります。これらのうちどれが適切であるかを判断するには、コメントアウトした魔法についてもっと知る必要があります。


ありがとう!これは役立ちます。仮に、doSomething()メソッドの目的は型変換になりSpecialSomethingます:受け取っSpecialSomethingた場合、オブジェクトを変更せずに返しますが、汎用SomethingInterfaceオブジェクトを受け取った場合、アルゴリズムを実行してSpecialSomethingオブジェクトに変換します。前提条件と事後条件は同じままなので、契約に違反したとは思わない。
エヴァン

1
@エヴァンああすごい...それは興味深いケースです。それは実際にはまったく問題ないかもしれません。私は考えることができる唯一のことは、あなたが既存のオブジェクトを返す代わりに、新しいオブジェクトを構築している場合、ということであるかもしれない誰かがブランドの新しいオブジェクトを返します。この方法によって異なります...しかし、そういったことは、人々が依存する可能性が破ることができるかどうか言語。誰かが、、を呼び出してy = doSomething(x)、それが3 x.setFoo(3)y.getFoo()返すことを見つけることは可能ですか?
Ixrec

言語によっては問題がありますが、SpecialSomething代わりにオブジェクトのコピーを返すだけで簡単に回避できます。純粋さのために、渡されたオブジェクトが特別な場合の最適化を無視し、オブジェクトであるSpecialSomethingために動作するはずなので、より大きな変換アルゴリズムを実行することもできSomethingInterfaceます。
エヴァン

1

LSPの違反ではありません。ただし、それでも「タイプチェックを行わない」ルールに違反しています。特別なことが自然に発生するようにコードを設計する方が良いでしょう。多分SomethingInterfaceこれを実現することができ、他の部材を必要とする、または多分あなたは抽象的な工場のどこかを注入する必要があります。

ただし、これは厳密なルールではないため、トレードオフに値するかどうかを判断する必要があります。現時点では、コードの匂いがあり、将来の機能強化の障害となる可能性があります。それを取り除くことは、かなり複雑なアーキテクチャを意味するかもしれません。より多くの情報がなければ、どちらが優れているかわかりません。


1

いいえ、特定の引数がインターフェイスAだけでなくA2も提供するという事実を利用しても、LSPに違反することはありません。

特別なパスには、より強い前提条件(それを取ることを決定する際にテストされたものを除く)や、より弱い事後条件がないことを確認してください。

C ++テンプレートは、たとえばInputIterators を要求することでパフォーマンスを向上させるためによく行いますが、sで呼び出された場合は追加の保証を提供しますRandomAccessIterator

代わりに実行時に決定する必要がある場合(たとえば、動的なキャストを使用する場合)、潜在的なすべての利益またはそれ以上を消費するパスを決定することに注意してください。

特殊なケースを利用することは、コードを複製する必要があるかもしれないのでDRY(自分自身を繰り返さないでください)や、より複雑なKISS(Keep it Simple)に反することがよくあります。


0

「型チェックを行わない」という原則と「インターフェースを分離する」という原則の間にはトレードオフがあります。多くのクラスがいくつかのタスクを実行するための実行可能ではあるが非効率的な手段を提供し、それらのいくつかがより良い手段を提供できる場合、タスクを実行できるアイテムの幅広いカテゴリを受け入れることができるコードが必要である場合(おそらく非効率的)しかし、タスクを可能な限り効率的に実行するには、すべてのオブジェクトに、より効率的なメソッドがサポートされているかどうかを示すメンバーを含むインターフェイスを実装し、別の場合はそれを使用する必要がありますオブジェクトを受け取るコードに、拡張インターフェースをサポートしているかどうかをチェックさせ、サポートしている場合はキャストします。

個人的には、前者のアプローチを好みますが、.NETのようなオブジェクト指向フレームワークでは、インターフェイスでデフォルトのメソッドを指定できるようにしたいと思います(より大きなインターフェイスでの作業が楽になります)。共通インターフェースにオプションのメソッドが含まれている場合、単一のラッパークラスは、さまざまな機能の組み合わせを持つオブジェクトを処理できますが、消費者には元のラップされたオブジェクトに存在する機能のみを約束します。多くの関数が異なるインターフェイスに分割される場合、ラップされたオブジェクトがサポートする必要があるインターフェイスのさまざまな組み合わせごとに、異なるラッパーオブジェクトが必要になります。


0

リスコフ置換原理は、スーパータイプの契約に従って機能するサブタイプに関するものです。したがって、Ixrecが書いたように、LSP違反であるかどうかを答えるのに十分な情報はありません。

ここで違反されているのは、オープンなクローズド原則です。新しい要件(SpecialSomethingマジックを実行)があり、既存のコードを変更する必要がある場合、間違いなくOCPに違反しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.