Liskovの置換の原則:サブタイプに、型には存在しない追加の動作が実装されている場合、このLSPの違反ですか?


8

私はより良く、よりクリーンなコードを書くために、SOLIDの原則について学んでいます。この点で、LSPは適切に把握するのが少し難しいことがわかっています。

私の疑問は、サブタイプSに余分なメソッドがあり、タイプTになかった場合、これは常にLSPの違反になるのでしょうか?はいの場合、どのようにextend私のクラスを行うのですか?

たとえば、Birdタイプがあるとしましょう。そして、そのサブタイプはEagleおよびHumming Birdです。現在、両方のサブタイプには、としていくつかの共通の動作がありBirdます。しかし、Eagle(一般的なBirdタイプには存在しない)優れた略奪行動もあるので、それを使用したい。したがって、今はこれを行うことができません:

Bird bird = new Eagle();

それで、Eagleそれらの追加の振る舞いを与えることはLSPを壊していますか?

はいの場合、それはLSP違反を引き起こすため、クラスを拡張できないことを意味しますか?

class Eagle extends Bird {
   //we are extending Bird since Eagle has some extra behavior also
}

クラスの拡張は、Open / Closed原則に従って許可する必要がありますか?

よろしくお願いします!はっきりとわかるように、LSPは私を他のものと同じように混乱させています。

編集:このSOの回答を参照してください。この場合Carも、のような追加の動作がある場合ChangeGear、LSPに違反します。では、LSPに違反せずにクラスを拡張するにはどうすればよいでしょうか。



私はそれを経験しました、しかしそれは私の質問に答えませんでした。私は実際に多くの答えを読みましたが、これまでのところ助けはありません。
user270386

1
それは上の答えにあります。読んでみてください。あるクラスを別のクラスから派生させるたびに、基本クラスと、人々がそれについて何を想定するかについて考えてください...そして、「これらの想定は私のサブクラスでも有効ですか?」そうでない場合は、設計を再考してください。
gnat

@gnat、ええ、私はそうしましたが、私は少し遅いです:)そして、私は通常、他の人が必要とするよりも多くの説明が必要です。デビッド・アーノの完全な答えを読んだ後、私は今その行に関係することができます。
user270386

1
@FrankHileman私たちの多くは、理想的とは言えないコンパイラとコードベースで作業しています。私たちがそうしなかったとしても、人間も彼らを尊重する方法を理解しているので、それは良いことです。
candied_orange 2018年

回答:


10

私の疑問は、サブタイプSに余分なメソッドがあり、タイプTになかった場合、これは常にLSPの違反になるのでしょうか?

非常に単純な答え:いいえ。

LSPのポイントは、のS代わりに使用できることですT。したがってTdelete関数を実装する場合は、Sそれも実装し、呼び出されたときに削除を実行する必要があります。ただし、提供されるS機能に加えて、追加の機能を自由に追加できTます。のコンシューマは、T与えられた場合S、この追加機能を認識しませんが、S直接利用するコンシューマが存在することを許可されます。

原則がどのように違反されるかについての非常に不自然な例は次のとおりです。

class T
{
    bool delete(Item x)
    {
        if (item exists)
        {
            delete it
            return true;
        }
        return false;
    }
}

class S extends T
{
    bool delete(Item x)
    {
        if (item doesn't exist)
        {
            add it
            return false;
        }
        return true;
    }
}

少し複雑な答え:いいえ、基本型の状態やその他の予想される動作に影響を与え始めない限り。

たとえば、以下は違反です。

class Point2D
{
    private readonly double _x;
    private readonly double _y;

    public virtual double X => _x;
    public virtual double Y => _y;

    public Point2D(double x, double y) => (_x, _y) = (x, y);
}

class MyPoint2D : Point2D
{
    private double _x;
    private double _y;

    public override double X => _x;
    public override double Y => _y;

    public MyPoint2D(double x, double y) : 
        base(x, y) => (_x, _y) = (x, y);

    public void Update(double x, double y) => (_x, _y) = (x, y);
}

タイプPoint2Dは不変です。その状態は変更できません。でMyPoint2D、私は意図的にその動作を回避して変更可能にしました。これはの履歴制約にPoint2D違反するため、LSPの違反になります。


おそらく、この回答への適切な追加、LSP違反なるようなdelete関数に追加できる動作の例でしょうか?
アンディハント

1
@ user270386:いいえ。LSPはサブクラスの構造ではなく、基本クラスの動作と比較したサブクラスコードの動作に関するものです。履歴の制約に違反するのは、それだけでは追加の機能ではありません。むしろ、この新しい機能が基本クラスで予期しない(たとえば、禁止されている)ことを行うと、制約に違反します(これには、言語で表現できるものだけでなく、ドキュメントでしか表現できないものも含まれます)。
FilipMilovanović18年

1
Tが厳密なインターフェイス、完全に抽象クラス、またはnullオブジェクトパターンでさえある場合、Sはほぼ構造について重要であることに注意する価値があります。Tはコードです。これは、必ずしも仕様、要件ドキュメント、製品所有者ではありません。Tを使用して常に保持する必要のある制約を明示する場合にのみ、LSP違反に注意します。すべてのクラスがそうするわけではありません。しかし、Sに削除するように指示すると、設計上、Sは何もせず、コードの残りの部分では問題ありません。それが本当かどうかTはあなたに言うことができません。これを行う前に確認する方がよいということしかわかりません。
candied_orange 2018年

1
(続き)IMO、これをすべて推進するのは代替性の要件です。代替可能性は、2つの異なる実装(動作)を、スーパータイプによって定義されたコントラクトによって規定された抽象化のより高いレベルで同等に扱うことができることに由来しています。その意味で、構造は目的を達成するための手段です。
FilipMilovanović18年

1
@DavidArno「基本タイプのコード」-仕様ではなく、必ずしもアクセス可能でもありません。
フランクヒルマン、

3

もちろん違います。Eagleオブジェクトが、Birdまたはサブクラスを必要とする任意のコードで使用でき、Birdが動作するように動作する場合、問題ありません。

もちろんEagleの動作は、そのようなオブジェクトであることを認識しているコードでのみ使用できます。Birdオブジェクトを必要とする任意のコードを使用できる一方で、一部のコードはEagleオブジェクトを明示的に作成してEagleオブジェクトとして使用することが期待されます。

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