2

私はここ数日、リスコフの置換原則を理解しようとしてきました。非常に典型的な長方形/正方形の例でいくつかのコードテストを行っているときに、以下のコードを作成し、それについて2つの質問を思いつきました。

質問1:スーパークラスとサブクラスの関係がある場合、インスタンスをスーパータイプとして宣言し、サブタイプとしてインスタンス化(新規作成)するのはなぜですか?

インターフェイスを介してポリモーフィズムを実行している場合、変数を次のように宣言してインスタンス化する理由を理解しています。

IAnimal dog = new Dog();

しかし、古いプログラミングクラスといくつかのブログの例でそれを思い出したので、継承を通じてポリモーフィズムを使用するとき、いくつかのコードがこのように変数を宣言するいくつかの例をまだ見ています

Animal dog = new Dog();

以下の私のコードでは、SquareはRectangleを継承しているため、次のように新しいSquareインスタンスを作成すると次のようになります。

Square sq = new Square();

それでも長方形として扱うことも、一般的な長方形のリストに追加することもできます。それでは、なぜ誰かがそれをRectangle = new Square()として宣言したいのでしょうか。私が見ていない利点、またはこれが必要となるシナリオはありますか?私が言ったように、以下の私のコードはうまく機能します。

namespace ConsoleApp
{
class Program
{
    static void Main(string[] args)
    {
        var rect = new Rectangle(300, 150);
        var sq = new Square(100);
        Rectangle liskov = new Square(50);

        var list = new List<Rectangle> {rect, sq, liskov};

        foreach(Rectangle r in list)
        {
            r.SetWidth(90);
            r.SetHeight(80);

            r.PrintSize();
            r.PrintMyType();

            Console.WriteLine("-----");
        }


        Console.ReadLine();
    }

    public class Rectangle
    {
        protected int _width;
        protected int _height;

        public Rectangle(int width, int height)
        {
            _width = width;
            _height = height;
        }

        public void PrintMyType()
        {
            Console.WriteLine(this.GetType());
        }

        public void PrintSize()
        {
            Console.WriteLine(string.Format("Width: {0}, Height: {1}", _width, _height));
        }

        public virtual void SetWidth(int value)
        {
            _width = value;
        }

        public virtual void SetHeight(int value)
        {
            _height = value;
        }

        public int Width { get { return _width; } }
        public int Height { get { return _height; } }
    }

    public class Square : Rectangle
    {
        public Square(int size) : base(size, size) {}

        public override void SetWidth(int value)
        {
            base.SetWidth(value);
            base.SetHeight(value);
        }

        public override void SetHeight(int value)
        {
            base.SetHeight(value);
            base.SetWidth(value);
        }
    }
}

}

これはリスコフの置換原則に違反しているはずですが、次の出力が得られます

"幅:90、高さ:80

ConsoleApp.Program + Rectangle

幅:80、高さ:80

ConsoleApp.Program + Square

幅:80、高さ:80 ConsoleApp.Program + Square

質問2:では、なぜ、またはどのようにこのコードサンプルがLSPを破壊しているのでしょうか。すべての辺の正方形の不変量が等しいために、辺が独立して変更できるのは長方形の不変量を壊すだけですか?それが理由である場合、LSP違反は理論上のものにすぎませんか?または、コードで、このコードが原則を破っているのをどのように見ることができますか?

編集:私が読んでいたLSPブログ記事の1つで出てきた3番目の質問を思いついたが、答えがなかったのでこれだ

質問3:オープンクローズの原則は、新しいクラス(継承またはインターフェース)を通じて新しい動作/機能を導入する必要があると述べています。たとえば、基本クラスにWriteLogメソッドがあり、前提条件はありませんが、メソッドをオーバーライドする新しいサブクラスを導入しますが、イベントが非常に重要な場合にのみ実際にログに書き込みます。新しい意図された機能(サブタイプで強化されている前提条件)、それでもLSPを壊しているでしょうか?この場合、2つの原則は互いに矛盾しているように見えます。

前もって感謝します。

4

2 に答える 2

3

質問1:スーパークラスとサブクラスの関係がある場合、インスタンスをスーパータイプとして宣言し、サブタイプとしてインスタンス化(新規作成)するのはなぜですか?

スーパータイプでこれを行う理由は、インターフェイスで行う理由と同じです。変数をスーパータイプではなく特定のサブタイプとして宣言する理由として挙げたすべての理由は、変数をサブタイプが実装するインターフェースではなく特定のサブタイプとして宣言する理由にも同様に当てはまります。

abstract class Car { ... }
public abstract class ToyotaCamery2011 extends Car ( ... )

class Garage {
    private Car car = new ToyotaCamery2011();
    public Car getCar() { return car; }
    ....
}

class Garage {
    private ToyotaCamery2011 toyotaCamery2011 = new ToyotaCamery2011();
    public Car getCar() { return toyotaCamery2011; }
    ....
}

Garageのメソッドのみを使用するすべてのメソッドCar、およびのパブリックインターフェイスGarageのみが表示され、にCar固有のものがない限りPrius2011、2つのクラスは実質的に同等です。どちらがより簡単に理解できますか?たとえば、どちらが現実の世界をより厳密にモデル化していますか?プリウス特有の方法、つまりプリウス特有のガレージを誤って使用しないようにするのはどれですか?私が新しい車を手に入れることにした場合、どれがほんの少しだけ保守しやすいですか?特定のサブタイプを使用して、コードは何らかの形で改善されていますか?


質問2:では、なぜ、またはどのようにこのコードサンプルがLSPを破壊しているのでしょうか。すべての辺の正方形の不変量が等しいために、辺が独立して変更できるのは長方形の不変量を壊すだけですか?それが理由である場合、LSP違反は理論上のものにすぎませんか?または、コードで、このコードが原則を破っているのをどのように見ることができますか?

約束/契約について話さずにLSPについて話すのは難しい。しかし、はい、Rectangleサイドを個別に変更できることが約束されている場合(より正式には、呼び出しの事後条件にRectangle.setWidth()Rectangle.getHeight()が影響を受けないことが含まれている場合)、ブレークLSPSquareから派生します。Rectangle

プログラムはこのプロパティに依存しないので、問題ありません。ただし、周長値または面積値を満たそうとしているプログラムを取り上げます。Rectangleそのようなプログラムは、独立した側面を持つアイデアに依存している可能性があります。

Rectangleaを入力として受け入れ、このプロパティ/動作に依存するクラスは、aを入力としてRectangle指定すると破損する可能性がありSquareます。このようなプログラムは、フープを飛び越えて(サブクラスの知識である)を探して許可しないか、独立したサイズに関してSquareのコントラクトを変更することができます。Rectangleそうすれば、使用するすべてのプログラムは、setLength()Square Rectangle`Rectangleを呼び出すたびにチェックできるようになり、LSPに違反しなくなります。setWidth()to see whether the adjacent side also changed and react accordingly. If it does the latter, thanderiving frmo

それは理論的なだけでなく、ソフトウェアに実際の影響を与える可能性がありますが、実際にはしばしば妥協されます。残念ながら、これはJavaでよく見られます。JavaのIteratorクラスは、remove()オプションのメソッドを提供します。イテレータを使用するクラスは、安全に使用できるかどうかを知るために、実装クラスやそのサブクラスについての知識を持っている必要がありますIterator.remove()。これはLSPに違反しますが、Javaで受け入れられている方法です。これにより、ソフトウェアの作成と保守がより複雑になり、バグの影響を受けやすくなります。


質問3:オープンクローズの原則は、新しいクラス(継承またはインターフェース)を通じて新しい動作/機能を導入する必要があると述べています。たとえば、基本クラスにWriteLogメソッドがあり、前提条件はありませんが、メソッドをオーバーライドする新しいサブクラスを導入しますが、イベントが非常に重要な場合にのみ実際にログに書き込みます。新しい意図された機能(サブタイプで強化されている前提条件)、それでもLSPを壊しているでしょうか?この場合、2つの原則は互いに矛盾しているように見えます。

前提条件とは、事後条件を意味していると思います。つまり、メソッドが満たすと約束していることについて説明しているのです。もしそうなら、私はLSP違反を見ません-メソッドスーパークラスが何も約束しない場合、サブクラスはそれが好きなことをすることができ、それでも完全に代替可能です。サブクラスが書き込む内容についてより選択的(「実際に書き込むだけ」)であるという事実は、特にスーパークラスが何も約束しないという事実に照らして、新しい機能です。

于 2011-02-02T08:16:25.610 に答える
0

なぜこれがリスコフの置換原則を破るべきだと思いますか?

LSPの本当の意味は、元のタイプではなくサブタイプのオブジェクトが渡された場合、メソッドは正しいことを行う必要があるということです。つまり、メソッドがそのタイプのオブジェクトを渡すと「正しいことをする」ことを証明できれば、それらのオブジェクトをサブタイプのオブジェクトに置き換えると「正しいことをする」ということです。

しかし、あなたの場合、メソッド呼び出しがないので、LSPはやや無関係です。

于 2011-02-02T07:43:28.527 に答える