web-dev-qa-db-ja.com

長方形から正方形を導出することは、リスコフの置換原則に違反していますか?

私はデザインに不慣れで、デザインの原則を学びます。

長方形から正方形を導出することは、リスコフの置換原則の違反の典型的な例であると書かれています。

その場合、正しいデザインは何でしょうか?

58
somaraj

推論は次のようなものだと思います。

長方形を受け入れてその幅を調整するメソッドがあるとしましょう。

public void SetWidth(Rectangle rect, int width)
{
    rect.Width = width;
}

長方形が何であるかを考えると、このテストに合格すると仮定することは完全に合理的であるはずです。

Rectangle rect = new Rectangle(50, 20); // width, height

SetWidth(rect, 100);

Assert.AreEqual(20, rect.Height);

...長方形の幅を変更しても、長方形の高さには影響しないためです。

ただし、Rectangleから新しいSquareクラスを派生させたとしましょう。定義上、正方形の高さと幅は常に同じです。そのテストをもう一度試してみましょう。

Rectangle rect = new Square(20); // both width and height

SetWidth(rect, 100);

Assert.AreEqual(20, rect.Height);

正方形の幅を100に設定すると高さも変わるため、このテストは失敗します。

したがって、長方形から正方形を導出することにより、リスコフの置換原則に違反します。

「is-a」ルールは「現実の世界」(正方形は間違いなく一種の長方形です)では意味がありますが、ソフトウェア設計の世界では必ずしもそうとは限りません。

編集

あなたの質問に答えるには、正しいデザインはおそらく、長方形と正方形の両方が、幅や高さに関する規則を強制しない共通の「ポリゴン」または「シェイプ」クラスから派生することです。

60
Matt Hamilton

答えは可変性に依存します。長方形と正方形のクラスが不変である場合、Squareは実際にはRectangleのサブタイプであり、最初に2番目から派生してもまったく問題ありません。そうしないと、RectangleSquareの両方がミューテーターなしでIRectangleを公開する可能性がありますが、どちらのタイプも適切に他方のサブタイプではないため、一方を他方から導出することは誤りです。

74
Anton Tykhyy

長方形から正方形を導出することが必ずしもLSPに違反することに同意しません。

Mattの例では、幅と高さが独立していることに依存するコードがある場合、実際にはLSPに違反しています。

ただし、仮定を破ることなく、コード内のあらゆる場所で長方形を正方形に置き換えることができれば、LSPに違反しているわけではありません。

つまり、抽象化長方形がyourソリューションで何を意味するかということになります。

5
Hans Malherbe

私は最近この問題にかなり苦労していて、私は自分の帽子をリングに追加すると思いました:

public class Rectangle {

    protected int height;    
    protected int width;

    public Rectangle (int height, int width) {
        this.height = height;
        this.width = width;
    }

    public int computeArea () { return this.height * this.width; }
    public int getHeight () { return this.height; }
    public int getWidth () { return this.width; }

}

public class Square extends Rectangle {

    public Square (int sideLength) {
        super(sideLength, sideLength);
    }

}

public class ResizableRectangle extends Rectangle {

    public ResizableRectangle (int height, int width) {
        super(height, width);
    }

    public void setHeight (int height) { this.height = height; }
    public void setWidth (int width) { this.width = width; }

}

最後のクラスResizableRectangleに注目してください。 「サイズ変更可能性」をサブクラスに移動することで、実際にモデルを改善しながらコードを再利用できます。このように考えてください。正方形は正方形のままで自由にサイズ変更することはできませんが、非正方形の長方形はサイズ変更できます。 allではありませんが、squareは長方形であるため、長方形のサイズを変更できます(また、「ID」を保持したまま自由にサイズを変更することはできません)。 (o_O)したがって、サイズ変更できない基本Rectangleクラスを作成することは理にかなっています。これは、some長方形の追加のプロパティだからです。

3
Chris Middleton

2つの(簡単にするためにpublic)プロパティwidth、heightを持つクラスRectangleがあると仮定しましょう。これらの2つのプロパティを変更できます:r.width = 1、r.height = 2。
ここで、正方形は長方形であると言います。ただし、「正方形は長方形のように動作する」という主張はありますが、正方形のオブジェクトに.width = 1と.height = 2を設定することはできません(高さを設定するとクラスが幅を調整し、その逆も同様です)。したがって、Square型のオブジェクトがRectangleのように動作せず、したがってそれらを(完全に)置き換えることができない場合が少なくとも1つあります。

2
VolkerK

問題は、記述されているのは実際には「タイプ」ではなく、累積的な創発的特性であるということです。

あなたが本当に持っているのは四辺形だけであり、「直角度」と「長方形」の両方は、角度と側面の特性から派生した単なる創発的なアーティファクトです。

「正方形」(または長方形)の概念全体は、オブジェクトのタイプやそれ自体のタイプではなく、オブジェクトの相互のプロパティと問題のオブジェクトのプロパティのコレクションの抽象的な表現にすぎません。

これは、タイプレス言語のコンテキストで問題を考えることが役立つ場合があります。これは、それが「正方形」であるかどうかを決定するのはタイプではなく、「正方形」であるかどうかを決定するオブジェクトの実際のプロパティだからです。

抽象化をさらに進めたい場合は、四角形があるとは言わないでしょうが、多角形または単なる形状があると思います。

1
Justin Ohms

ソフトウェアが現実の世界を表現できるようにするためのOOD/OOP技術が存在すると私は信じています。現実の世界では、正方形は等しい辺を持つ長方形です。正方形は、正方形であると決めたからではなく、辺が等しいという理由だけで正方形です。したがって、OOプログラムはそれを処理する必要があります。もちろん、オブジェクトをインスタンス化するルーチンがオブジェクトを正方形にしたい場合は、lengthプロパティとwidthプロパティを同じものとして指定できます。オブジェクトを使用するプログラムが後でそれが正方形であるかどうかを知る必要がある場合は、それを尋ねるだけで済みます。オブジェクトは「Square」と呼ばれる読み取り専用のブールプロパティを持つことができます。呼び出しルーチンがそれを呼び出すと、オブジェクトは(長さ=幅)これは、rectangleオブジェクトが不変であっても当てはまる可能性があります。さらに、rectangleが実際に不変である場合は、Squareプロパティの値をコンストラクターで設定し、それを使用して実行できます。これは問題ですか?LSPでは、サブオブジェクトを適用するために不変である必要があり、長方形のサブオブジェクトである正方形が違反の例としてよく使用されますが、使用する場合は適切な設計ではないようです。ルーチンはオブジェクトを「objSquare」として呼び出し、その内部の詳細を知っている必要があります。長方形が正方形であるかどうかは気にしませんか?そして、それは長方形の方法が関係なく正しいからです。 LSPに違反した場合のより良い例はありますか?

もう1つの質問:オブジェクトはどのようにして不変になりますか?インスタンス化時に設定できる「不変」プロパティはありますか?

私は答えを見つけました、そしてそれは私が期待したものです。私はVB .NET開発者なので、興味があります。しかし、概念は言語間で同じです。VB .NETプロパティを読み取り専用にすることで不変クラスを作成し、Newコンストラクターを使用して、オブジェクトの作成時にインスタンス化ルーチンがプロパティ値を指定できるようにします。一部のプロパティに定数を使用することもでき、それらは常に同じになります。作成以降、オブジェクトは不変です。

1
user338841