インスタンスをスーパータイプとして宣言しますが、サブタイプとしてインスタンス化する理由とリスコフ代替原則
-
28-10-2019 - |
質問
私は数日間リスコフ代替原則を理解しようとしてきましたが、非常に典型的な長方形/正方形の例でいくつかのコードテストを行っている間、以下にコードを作成し、それについて2つの質問を思いつきました。
質問1:スーパークラス/サブクラスの関係がある場合、なぜインスタンスをスーパータイプとして宣言しますが、サブタイプとしてインスタンス(新しい)をインスタンス化するのですか?
インターフェイスを通じて多型を行っている場合、この方法で変数を宣言し、インスタンス化したい理由を理解しています。
IAnimal dog = new Dog();
しかし、古いプログラミングクラスといくつかのブログの例でそれについて思い出すので、継承を通じて多型を使用する場合、いくつかのコードがこの方法で変数を宣言する例をいくつか見ています
Animal dog = new Dog();
以下の私のコードでは、Squareは長方形から継承するため、この方法で新しいSquareインスタンスを作成すると:
Square sq = new Square();
それはまだ長方形として扱うことも、長方形の汎用リストに追加することもできますが、なぜ誰かがそれを長方形= 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つの原則は互いに矛盾しているように見えます。
前もって感謝します。
解決
質問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()を含める必要はありません)、次に Square
派生 Rectangle
LSPを破る。
あなたのプログラムはこのプロパティに依存していないので、それは大丈夫です。ただし、境界値または面積値を満たそうとしているプログラムを取得します。そのようなプログラムは、その考えに依存するかもしれません Rectangle
独立した側面があります。
aを受け入れるクラス Rectangle
入力として、このプロパティ/動作に依存します Rectangle
与えられたときに破損する可能性があります Square
入力として。このようなプログラムは、フープを飛び越えて探すことができます。 Square
(これはサブクラスの知識です)、またはの契約を変更できます Rectangle
独立したサイズに関して。次に、使用するすべてのプログラム Rectangle
通話ごとに確認できます setWidth()
またはsetLength()to see whether the adjacent side also changed and react accordingly. If it does the latter, than
四角deriving frmo
長方形 `はもはやLSPの違反ではありません。
理論的なだけでなく、ソフトウェアに実際の影響を与える可能性がありますが、実際には妥協されることがよくあります。残念ながらJavaでこれを見ます。 Java's Iterator
クラスはaを提供します remove()
オプションの方法。イテレーターを使用するクラスは、実装クラスおよび/またはそのサブクラスに関する知識を持っている必要があります。 Iterator.remove()
. 。これはLSPに違反しますが、Javaで受け入れられた慣行です。これにより、ソフトウェアの書き込みと維持をより複雑で維持し、バグの影響を受けやすくなります。
質問3:オープンな原則は、新しいクラス(継承またはインターフェイス)を通じて新しい行動/機能を導入する必要があると述べています。たとえば、前提条件がないベースクラスにWritelogメソッドがある場合は、メソッドをオーバーライドする新しいサブクラスを導入しますが、イベントが非常に重要な場合にのみログに書き込みます。新しい意図された機能(サブタイプで硬化する前提条件)、それはまだLSPを破っているのでしょうか?この場合、2つの原則は互いに矛盾しているように見えます。
前提条件を言うとき、私はあなたが事後条件を意味すると思います - あなたは、方法が満たすことを約束するものについて説明しています。もしそうなら、LSP違反はありません - メソッドのスーパークラスが何も約束しない場合、サブクラスは好きなことを行い、それでも完全に代用可能です。サブクラスがより選択的であるという事実は、特にスーパークラスが何も約束しないという事実に照らして、それが書いていることについてより選択的(「実際に書く」)です。
他のヒント
なぜこれがリスコフの代替原則を破るべきだと思いますか?
LSPが実際にあるのは、元のタイプではなくサブタイプのオブジェクトが渡された場合、メソッドが正しいことをするべきだということです。つまり、タイプのオブジェクトを渡すと、メソッドが「正しいことを行う」ことを証明できる場合、それらのオブジェクトをサブタイプのオブジェクトに置き換えると「正しいことをする」ことができます。
しかし、あなたの場合、メソッド呼び出しがないため、LSPはやや無関係です。