私はデザインを始めたばかりで、デザインの原則を学んでいます。
長方形から正方形を導出することは、リスコフの置換原理に違反する典型的な例であると述べています。
その場合、正しい設計は何ですか?
私はデザインを始めたばかりで、デザインの原則を学んでいます。
長方形から正方形を導出することは、リスコフの置換原理に違反する典型的な例であると述べています。
その場合、正しい設計は何ですか?
答えは可変性に依存します。長方形と正方形のクラスが不変である場合、それSquare
は実際にはのサブタイプでRectangle
あり、最初に2番目から派生することはまったく問題ありません。そうでなければ、ミューテーターなしで両方を公開する可能性がRectangle
ありますが、どちらのタイプも適切に他方のサブタイプではないため、一方を他方から導出することは間違っています。Square
IRectangle
推論は次のようなものだと思います。
長方形を受け入れ、その幅を調整するメソッドがあるとします。
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」ルールは「現実の世界」(正方形は確かに一種の長方形です)では意味がありますが、ソフトウェア設計の世界では必ずしもそうとは限りません。
編集
あなたの質問に答えるには、正しいデザインは、長方形と正方形の両方が、幅や高さに関する規則を強制しない共通の「ポリゴン」または「シェイプ」クラスから派生することであるはずです。
長方形から正方形を導出することが必ずしもLSPに違反することに同意しません。
Mattの例では、幅と高さが独立していることに依存するコードがある場合、実際にはLSPに違反します。
ただし、仮定を破ることなく、コード内のあらゆる場所で長方形を正方形に置き換えることができれば、LSPに違反しているわけではありません。
つまり、ソリューションで抽象化長方形が何を意味するかということです。
私は最近、この問題にかなり苦労しており、リングに帽子を追加しようと考えました。
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
。「サイズ変更可能性」をサブクラスに移動することで、実際にモデルを改善しながらコードを再利用できます。次のように考えてみてください: 正方形は、正方形のままで自由にサイズ変更することはできませんが、正方形ではない長方形は可能です。ただし、正方形は長方形であるため、すべての長方形のサイズを変更できるわけではありません (「同一性」を保持したまま自由にサイズ変更することはできません)。(o_O) したがって、サイズ変更できない基本クラスを作成することは理にかなっています。これは、一部の四角形の追加のプロパティであるためです。Rectangle
問題は、記述されているのは実際には「タイプ」ではなく、累積的な緊急プロパティであることです。
あなたが実際に持っているのは四角形だけであり、「直角」と「直角」の両方が角度と辺のプロパティから派生した創発的なアーティファクトにすぎません.
「正方形」(または長方形) の全体的な概念は、オブジェクトの相互関係および問題のオブジェクトのプロパティのコレクションの抽象的な表現であり、オブジェクト自体のタイプではありません。
これは、型のない言語のコンテキストで問題を考えることが役立つ場所です。「正方形」であるかどうかを決定するのは型ではなく、「正方形」であるかどうかを決定するオブジェクトの実際のプロパティであるためです。
抽象化をさらに進めたい場合は、四角形を持っているとは言えませんが、多角形または単なる形状を持っているとは言えません。
私は、ソフトウェアが現実世界を表現できるようにするための OOD/OOP 技術が存在すると信じています。現実の世界では、正方形は辺が等しい長方形です。正方形が正方形である理由は、辺が等しいからであって、正方形であると決めたからではありません。したがって、OO プログラムはこれに対処する必要があります。もちろん、オブジェクトをインスタンス化するルーチンがオブジェクトを正方形にしたい場合は、長さプロパティと幅プロパティを同じ量に指定することができます。オブジェクトを使用するプログラムが後でそれが正方形であるかどうかを知る必要がある場合は、それを確認するだけで済みます。オブジェクトは、「Square」と呼ばれる読み取り専用のブール型プロパティを持つことができます。呼び出しルーチンがそれを呼び出すと、オブジェクトは戻ることができます (長さ = 幅)。これは、四角形オブジェクトが不変であっても当てはまります。さらに、長方形が実際に不変の場合、Square プロパティの値は、コンストラクターで設定して実行できます。では、なぜこれが問題になるのでしょうか。LSP では、サブオブジェクトを適用するには不変である必要があり、長方形のサブオブジェクトである正方形は、その違反の例としてよく使用されます。しかし、それは良い設計ではないようです。なぜなら、使用ルーチンがオブジェクトを「objSquare」として呼び出すとき、その内部の詳細を知らなければならないからです。長方形が正方形であるかどうかを気にしない方がよいのではないでしょうか? それは、長方形のメソッドが関係なく正しいためです。LSP に違反した場合のより良い例はありますか? しかし、それは良い設計ではないようです。なぜなら、使用ルーチンがオブジェクトを「objSquare」として呼び出すとき、その内部の詳細を知らなければならないからです。長方形が正方形であるかどうかを気にしない方がよいのではないでしょうか? それは、長方形のメソッドが関係なく正しいためです。LSP に違反した場合のより良い例はありますか? しかし、それは良い設計ではないようです。なぜなら、使用ルーチンがオブジェクトを「objSquare」として呼び出すとき、その内部の詳細を知らなければならないからです。長方形が正方形であるかどうかを気にしない方がよいのではないでしょうか? それは、長方形のメソッドが関係なく正しいためです。LSP に違反した場合のより良い例はありますか?
もう 1 つの質問: オブジェクトを不変にする方法を教えてください。インスタンス化時に設定できる「不変」プロパティはありますか?
私は答えを見つけました、そしてそれは私が期待したものです。私は VB .NET 開発者なので、これに興味があります。しかし、概念はどの言語でも同じです。VB .NET では、プロパティを読み取り専用にして不変クラスを作成し、New コンストラクタを使用して、インスタンス化ルーチンがオブジェクトの作成時にプロパティ値を指定できるようにします。一部のプロパティには定数を使用することもでき、それらは常に同じになります。オブジェクトは作成から不変です。