web-dev-qa-db-ja.com

SetWidthメソッドとSetHeightメソッドをオーバーライドすると、SquareがRectangleから継承すると問題が発生するのはなぜですか?

SquareがRectangleの一種である場合、SquareがRectangleから継承できないのはなぜですか?それともなぜ悪いデザインなのでしょうか?

私は人々が言うのを聞いた:

四角形を四角形から派生させた場合、四角形は四角形を期待する場所ならどこでも使用できるはずです。

ここの問題は何ですか?そして、Squareが四角形を期待する場所で使用できるのはなぜですか? Squareオブジェクトを作成し、SquareのSetWidthメソッドとSetHeightメソッドをオーバーライドした場合にのみ問題が発生するのはなぜですか?

Rectangle基本クラスにSetWidthメソッドとSetHeightメソッドがあり、Rectangle参照がSquareを指している場合、一方を設定すると他方に変更されるため、SetWidthとSetHeightは意味がありません。この場合、SquareはRectangleを使用したLiskov置換テストに失敗し、SquareがRectangleから継承するという抽象化は適切ではありません。

誰かが上記の議論を説明できますか?繰り返しますが、SquareでSetWidthメソッドとSetHeightメソッドをオーバーライドすると、この問題は解決しませんか?

私も聞いた/読んだ:

本当の問題は、長方形をモデル化するのではなく、「再形成可能な長方形」、つまり、作成後に幅または高さを変更できる長方形をモデリングしていることです(それでも、同じオブジェクトと見なします)。この方法で四角形クラスを見ると、四角形は形を変えられず、なおかつ四角形であることができないため(一般的に)、四角形は「変形可能な四角形」ではないことは明らかです。数学的には、可変性は数学的な文脈では意味をなさないため、問題は発生しません

ここでは、「サイズ変更可能」が正しい用語だと思います。四角形は「サイズ変更可能」で、正方形も同様です。上記の議論に何か欠けていますか?正方形は、長方形と同じようにサイズを変更できます。

109
user793468

基本的に、私たちは物事が賢く振る舞うことを望みます。

次の問題を検討してください。

長方形のグループが与えられ、それらの面積を10%増やしたい。ですから、長方形の長さを以前の1.1倍に設定します。

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    rectangle.Length = rectangle.Length * 1.1;
  }
}

この場合、すべての四角形の長さが10%増加し、面積が10%増加します。残念ながら、実際に誰かが正方形と長方形の混合物を渡してくれました。長方形の長さを変更すると、幅も変更されました。

四角形のコレクションを使用するようにすべての単体テストを作成したので、単体テストは成功しました。私は今、自分のアプリケーションに微妙なバグを導入しましたが、何ヶ月も気付かれない可能性があります。

さらに悪いことに、会計のジムは私の方法を見て、正方形を私の方法に渡すと、サイズが21%と非常に良くなるという事実を使用する他のコードを記述します。ジムは幸せで誰も賢くありません。

ジムは別の部門に優秀な仕事に昇進します。アルフレッドはジュニアとして入社しました。 AdvertisingのJillは、最初のバグレポートで、正方形をこのメソッドに渡すと21%増加し、バグの修正を望んでいると報告しています。アルフレッドは、四角形と四角形がコードのあらゆる場所で使用されていることを確認し、継承チェーンを解除することは不可能であることを認識しています。また、経理のソースコードにもアクセスできません。だからアルフレッドはこのようにバグを修正します:

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

アルフレッドは彼の超ハッキングスキルに満足しており、ジルはバグが修正されたことを承認しました。

アカウンティングはIncreaseRectangleSizeByTenPercentメソッドに四角を渡すことができ、面積が21%増加することに依存していたため、来月は誰も支払いを受けません。会社全体が「優先度1バグ修正」モードに入り、問題の原因を突き止めます。彼らは問題をアルフレッドの修正までたどります。彼らは会計と広告の両方を幸せに保つ必要があることを知っています。そのため、次のようにメソッド呼び出しでユーザーを識別することで問題を修正します。

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  IncreaseRectangleSizeByTenPercent(
    rectangles, 
    new User() { Department = Department.Accounting });
}

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    else if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

等々。

この逸話は、プログラマーが毎日直面する現実の状況に基づいています。 Liskov Substitutionの原則に違反すると、非常に微妙なバグが発生する可能性があります。作成されてから数年しか経っていないため、違反を修正すると多くの問題が解決され、修正されませんそれはあなたの最大のクライアントを怒らせます。

この問題を解決するには、2つの現実的な方法があります。

最初の方法は、Rectangleを不変にすることです。 RectangleのユーザーがLengthおよびWidthプロパティを変更できない場合、この問題はなくなります。長さと幅が異なる四角形が必要な場合は、新しい四角形を作成します。正方形は長方形から幸せに継承できます。

2番目の方法は、正方形と長方形の間の継承チェーンを切断することです。正方形が単一のSideLengthプロパティを持つものとして定義され、長方形がLengthおよびWidthプロパティを持ち、継承がない場合、長方形を予期して誤って分割することは不可能ですと正方形を取得します。 C#の用語では、長方形クラスをsealできます。これにより、取得するすべての長方形が実際に長方形になることが保証されます。

この場合、問題を修正する「不変オブジェクト」の方法が好きです。長方形のアイデンティティは、その長さと幅です。オブジェクトのIDを変更する場合、本当に必要なのはnewオブジェクトであることは理にかなっています。古い顧客を失い、新しい顧客を獲得した場合、Customer.Idフィールドを古い顧客から新しい顧客に、新しいCustomerを作成します。

Liskov Substitution原則の違反は現実の世界では一般的です。これは主に、そこにある多くのコードが、無能な/時間のプレッシャーを受けている/気にしない/間違いを犯す人々によって書かれているためです。それは、いくつかの非常に厄介な問題を引き起こす可能性があります。ほとんどの場合、代わりに 継承より優先する構成 を使用します。

189
Stephen

すべてのオブジェクトが不変である場合、問題はありません。すべての正方形も長方形です。長方形のすべてのプロパティは、正方形のプロパティでもあります。

オブジェクトを変更する機能を追加すると、問題が始まります。または本当に-プロパティのゲッターを読み取るだけでなく、オブジェクトに引数を渡し始めるとき。

幅や高さの変更など、四角形のすべての不変式を維持するのではなく、四角形クラスのすべての不変量を維持する、四角形に対して行うことができる変更があります。突然、Rectangleの動作はそのプロパティだけでなく、可能な変更でもあります。これは、四角形から得られるものだけではなく、何を入力できるかでもあります。

RectangleにメソッドsetWidthがあり、幅を変更して高さを変更しないと記載されている場合、Squareは互換性のあるメソッドを持つことができません。高さではなく幅を変更すると、結果は有効な四角形ではなくなります。 setWidthを使用するときにSquareの幅と高さの両方を変更することを選択した場合、RectangleのsetWidthの仕様を実装していません。勝てません。

四角形と四角形に「入れる」ことができるもの、それらに送信できるメッセージを見ると、四角形に有効に送信できるメッセージはすべて、四角形にも送信できることがわかります。

それは、共分散対逆分散の問題です。

スーパークラスが期待されるすべてのケースでインスタンスを使用できる適切なサブクラスのメソッドでは、各メソッドが次のことを行う必要があります。

  • スーパークラスが返す値のみを返します。つまり、戻り値の型は、スーパークラスメソッドの戻り値の型のサブタイプでなければなりません。リターンは共変です。
  • スーパータイプが受け入れるすべての値を受け入れます。つまり、引数の型は、スーパークラスメソッドの引数の型のスーパータイプでなければなりません。引数は反変です。

したがって、RectangleとSquareに戻ります。SquareがRectangleのサブクラスになることができるかどうかは、Rectangleが持つメソッドに完全に依存します。

Rectangleに幅と高さの個別のセッターがある場合、Squareは適切なサブクラスを作成しません。

同様に、長方形でcompareTo(Rectangle)を使用し、正方形でcompareTo(Square)を使用するなど、いくつかのメソッドを引数で共変にすると、正方形を長方形として使用するときに問題が発生します。

SquareとRectangleを互換性があるように設計すると、おそらく機能しますが、一緒に開発する必要があります。そうしないと機能しません。

30
lrn

ここには良い答えがたくさんあります。特にスティーブンの回答は、代替原則の違反がチーム間の現実世界の衝突につながる理由を説明するのに役立ちます。

LSPのその他の違反のメタファーとして使用するのではなく、長方形と正方形の特定の問題について簡単に説明したいと思いました。

めったに言及されない四角形が特別な種類の四角形に追加の問題があり、それは次のとおりです。四角形と四角形で停止する理由?正方形が特別な種類の長方形であると言っても構わないと思っているのなら、きっと私たちも言ってくれるでしょう:

  • 正方形は特別な種類のひし形です-正方形の角度を持つひし形です。
  • 菱形は特別な種類の平行四辺形です-等しい辺を持つ平行四辺形です。
  • 長方形は特別な種類の平行四辺形です-正方形の角度を持つ平行四辺形です
  • 長方形、正方形、平行四辺形はすべて特別な種類の台形です-それらは2組の平行な辺を持つ台形です
  • 上記のすべては特別な種類の四辺形です
  • 上記はすべて特別な種類の平面形状です
  • 等々;私はしばらくここに行くことができました。

すべての関係がここにあるべきですか? C#やJavaなどのクラス継承ベースの言語は、複数の異なる種類の制約を持つこの種の複雑な関係を表すようには設計されていませんでした。サブタイプの関係を持つクラスとしてこれらの事の。

17
Eric Lippert

数学的な観点から、正方形は長方形です。数学者が正方形を修正して正方形の契約に準拠しなくなった場合、長方形に変わります。

しかしOOの設計では、これは問題です。オブジェクトとは何かということであり、これにはbehaviorsと状態が含まれます。正方形のオブジェクトを保持している場合、他の誰かがそれを長方形に変更し、それは私自身のせいではなく、正方形の契約に違反します。これにより、あらゆる種類の悪いことが起こります。

ここでの重要な要素はmutabilityです。一度構築すると形状は変化しますか?

  • 可変:一度構築された形状の変更が許可されている場合、正方形は長方形とis-aの関係を持つことはできません。長方形のコントラクトには、反対側の辺は同じ長さでなければならないという制約が含まれていますが、隣接する辺は同じである必要はありません。正方形には、4つの等しい辺が必要です。長方形のインターフェースを介して正方形を変更すると、正方形の契約に違反する可能性があります。

  • 不変:一度作成された形状を変更できない場合、正方形のオブジェクトも常に長方形のコントラクトを満たさなければなりません。正方形は長方形とis-aの関係を持つことができます。

どちらの場合も、1つ以上の変更を加えた状態に基づいて、正方形に新しい形状を作成するよう依頼することができます。たとえば、「この正方形に基づいて新しい長方形を作成します。ただし、AとCの反対側の長さは2倍です。」新しいオブジェクトが構築されているので、元の正方形は引き続きその契約に従います。

14
user22815

そして、Squareが四角形を期待する場所で使用できるのはなぜですか?

それは、サブタイプであることの意味の一部だからです(Liskov置換原理も参照)。あなたが行うことができます、これを行うことができる必要があります:

Square s = new Square(5);
Rect r = s;
doSomethingWith(r); // written assuming a Rect, actually calls Square methods

OOPを使用する場合、実際にはこれを常に(時にはより暗黙的に)行います。

また、SquareのSetWidthメソッドとSetHeightメソッドをオーバーライドすると、なぜ問題が発生するのでしょうか?

Squareの値を適切に上書きできないためです。なぜなら、正方形できません「長方形のようにサイズを変更する」ためです。長方形の高さが変わっても、幅は変わりません。ただし、正方形の高さが変化すると、それに応じて幅も変化する必要があります。問題は単にサイズ変更が可能なだけではなく、両方の次元で個別にサイズ変更が可能であることです。

13
user7043

あなたが説明しているものはLiskov Substitution Principleと呼ばれるものに反しています。 LSPの基本的な考え方は、特定のクラスのインスタンスを使用するときはいつでも、そのクラスの任意のサブクラスのインスタンスで常にスワップできる必要があるということですwithoutバグの紹介。

四角形の正方形の問題は、リスコフを導入するための実際には非常に良い方法ではありません。これは、実際には非常に微妙な例を使用して幅広い原理を説明しようとし、最も一般的な直観的なものの1つに反するものですすべての数学の定義。そのためEllipse-Circle問題と呼ばれる人もいますが、これに関してはほんの少しだけ優れています。より良いアプローチは、私がParallelogram-Rectangle問題と呼んでいるものを使用して、少し後退することです。これにより、物事がはるかに理解しやすくなります。

平行四辺形は、2組の平行な辺を持つ四辺形です。また、2組の合同角度があります。これらの線に沿って平行四辺形オブジェクトを想像するのは難しくありません。

class Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

長方形を考える一般的な方法の1つは、直角の平行四辺形です。 一見すると、これはRectangleをParallelogramから継承するのに適した候補になっているように見えるかもしれませんなので、そのすべてのおいしいコードを再利用できます。しかしながら:

class Rectangle extends Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* BUG: Liskov violations ahead */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

これらの2つの関数が四角形にバグをもたらすのはなぜですか? 問題は、長方形の角度を変更できないことです:それらは常に90度として定義されているため、このインターフェイスは、Parallelogramから継承するRectangleでは実際に機能しません。四角形を平行四辺形を期待するコードに入れ替え、そのコードが角度を変更しようとすると、ほぼ間違いなくバグが発生します。サブクラスで書き込み可能なものを取得して読み取り専用にしましたが、これはLiskov違反です。

これはどのようにこれを正方形と長方形に適用しますか?

値を設定できるとは、通常、値を書き込むことができるよりも少し強いことを意味します。ある程度の独占性を意味します:値を設定した場合、特別な状況でない限り、再度設定するまでその値のままになります可能な値には多くの用途があります書き込まれますが、設定されたままではありませんが、一度設定した場所にとどまっている値に依存する場合も多くあります。そして、ここで別の問題が発生します。

class Square extends Rectangle {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};

    /* BUG: More Liskov violations */
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* Liskov violations inherited from Rectangle */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

SquareクラスはRectangleからバグを継承しましたが、新しいバグがいくつかあります。 setSideAとsetSideBの問題は、これらのどちらも真に設定可能ではなくなったことです。値をどちらかに書き込むことはできますが、もう一方が書き込まれていると、下から変更されます。これを、互いに独立して辺を設定できるかどうかに依存するコードの平行四辺形と交換すると、おかしくなります。

それが問題であり、それがLiskovの紹介としてRectangle-Squareを使用することに問題がある理由です。 Rectangle-Squareは、何かに書き込み可能setそれ、そしてそれは何かを設定することができることとそれを読み取り専用にすることよりもはるかに微妙な違いです。 Rectangle-Squareにはまだ例として価値があります。これは、注意が必要なかなり一般的な落とし穴を文書化しているためですが、の紹介として使用しないでください例。学習者にまず基本的な基礎を学んでもらい、、次により難しいものを投げます。

9
The Spooniest

サブタイピングは動作に関するものです。

タイプBがタイプAのサブタイプであるためには、タイプAがサポートするすべての操作を同じセマンティクスでサポートする必要があります(「振る舞い」の空想的な話)。 すべてのBがAであるという根拠を使用すると、動作しない動作-動作互換性には最終決定権があります。ほとんどの場合、「BはAの一種」は「BはAのように動作する」と重複しますが、常にではありません

例:

実数のセットを考えます。どの言語でも、操作のサポートを期待できます+-*/。次に、正の整数のセット({1、2、3、...})を考えます。明らかに、すべての正の整数も実数です。しかし、正の整数の型は実数の型のサブタイプですか? 4つの演算を見て、正の整数が実数と同じように動作するかどうかを見てみましょう。

  • +:正の整数を問題なく追加できます。
  • -:すべての正の整数の減算が正の整数になるわけではありません。例えば。 3 - 5
  • *:正の整数を問題なく乗算できます。
  • /:常に正の整数を除算して正の整数を取得できるとは限りません。例えば。 5 / 3

したがって、正の整数は実数のサブセットですが、サブタイプではありません。有限サイズの整数についても同様の議論をすることができます。明らかに、すべての32ビット整数は64ビット整数でもありますが、32_BIT_MAX + 1は、タイプごとに異なる結果を提供します。したがって、プログラムをいくつか提供し、すべての32ビット整数変数の型を64ビット整数に変更した場合、プログラムの動作が異なる可能性があります(ほとんどの場合、が誤って)。

もちろん、+ 32ビット整数の場合、結果は64ビット整数になりますが、2ビットの32ビット数値を追加するたびに64ビットのスペースを予約する必要があります。それはあなたのメモリのニーズに応じてあなたに受け入れられるかもしれないし、そうでないかもしれません。

なぜこれが問題になるのですか?

プログラムが正しいことは重要です。それは間違いなく、プログラムにとって最も重要な特性です。プログラムが一部のタイプAに対して正しい場合、プログラムが一部のサブタイプBに対して引き続き正しくなることを保証する唯一の方法は、BAあらゆる点で。

つまり、タイプはRectanglesであり、その仕様によると、サイドは個別に変更できるということです。 Rectanglesを使用し、実装が仕様に従っていることを前提とするプログラムをいくつか作成しました。次に、Squareというサブタイプを導入しました。そのサイドは個別にサイズ変更できません。その結果、四角形のサイズを変更するほとんどのプログラムは正しくありません。

8
Doval

SquareがRectangleの一種である場合、SquareがRectangleから継承できないのはなぜですか?それともなぜ悪いデザインなのでしょうか?

まず最初に、自問してみてください正方形が長方形であると思う理由

もちろん、ほとんどの人が小学校でそれを学びました、そしてそれは明白に思われるでしょう。長方形は90度の角度を持つ4辺の形状であり、正方形はこれらすべての特性を満たします。では、正方形は長方形ではないのですか?

ただし、すべてのことは、オブジェクトをグループ化するための最初の基準、これらのオブジェクトをどのようなコンテキストで見ているかに依存します。ジオメトリでは、形状はそれらの点、線、および天使の特性に基づいて分類されます。

したがって、「正方形は長方形の一種です」と言う前に、まず自分自身に問いかける必要がありますこれは私が気にしている基準に基づいていますか

ほとんどの場合、あなたが気にすることはまったくありません。 GUI、グラフィックス、ビデオゲームなどの形状をモデル化するシステムの大部分は、主にオブジェクトの幾何学的なグループ化に関係していませんが、それは動作です。正方形が幾何学的な意味で長方形の一種であることが重要であるシステムに取り組んだことがありますか。 4つの側面と90度の角度があることを知っていれば、それはあなたに何を与えるでしょうか?

あなたは静的システムをモデル化しているのではなく、物事が発生する(形状が作成、破壊、変更、描画されるなど)動的システムをモデル化しています。このコンテキストでは、オブジェクト間の共有動作に注意します。主な関心事は、図形で何ができるか、一貫性のあるシステムを維持するために維持する必要があるルールであるためです。

このコンテキストでは、正方形はほとんど間違いなく長方形ではありません、正方形を変更する方法を管理するルールは長方形と同じではないためです。したがって、それらは同じタイプのものではありません。

その場合は、そのようにモデル化しないでください。なんで?それはあなたに不必要な制限以外何も得ません。

Squareオブジェクトを作成し、SquareのSetWidthメソッドとSetHeightメソッドをオーバーライドした場合にのみ問題が発生するのはなぜですか?

あなたが実際にコードで彼らが同じものではないことを述べているけれどもあなたがそれをするなら。あなたのコードは、正方形はこのように振る舞い、長方形はそのように振る舞うと言っていますが、それらはまだ同じです。

2つの異なる動作を定義しただけなので、気になるコンテキストではこれらは明らかに同じではありません。それで、あなたが気にしない状況でそれらが類似しているだけであるのに、それらが同じであると偽るのはなぜですか?

これは、開発者がモデル化したいドメインに到達したときの重大な問題を浮き彫りにします。ドメイン内のオブジェクトについて考える前に、興味のあるコンテキストを明確にすることが非常に重要です。あなたは何に興味がありますか。何千年も前に、ギリシャ人は線と形の天使の共有特性を気にし、それらに基づいてそれらをグループ化しました。それが気にならなければグループ化を続けることを強いられるという意味ではありません(ソフトウェアのモデリングの99%では気にしません)。

この質問に対する回答の多くは、グループ化の動作に関するサブタイピングに焦点を当てています 'それらにルールを使用します。

しかし、ルールに従うためだけにこれを行っているわけではないことを理解することは非常に重要です。あなたがこれをしているのは、ほとんどの場合、これがあなたが実際に気にしていることだからです。正方形と長方形が同じ内部天使を共有しているかどうかは気にしません。あなたは彼らがまだ正方形と長方形である間に彼らが何ができるかについて気にします。オブジェクトの動作のルールに基づいてシステムを変更することに焦点を当てたシステムをモデル化しているので、オブジェクトの動作を気にします。

6
Cormac Mulhall

SquareがRectangleの一種である場合、SquareがRectangleから継承できないのはなぜですか?

問題は、物事が実際に何らかの形で関連している場合、モデリング後もまったく同じように関連している必要があるという考えにあります。

モデリングで最も重要なことは、共通の属性と共通の動作を識別し、それらを基本クラスで定義し、子クラスに追加の属性を追加することです。

あなたの例の問題は、それが完全に抽象的なことです。誰もがそのクラスをどのような目的で使用するのかを知らない限り、どのような設計を行うべきかを推測するのは困難です。ただし、高さ、幅、サイズ変更のみを本当にしたい場合は、次のようにするのがより論理的です。

  • squareを基本クラスとして定義し、widthパラメータとresize(double factor)を使用して、指定された係数で幅をサイズ変更します
  • rectangleクラスとSquareのサブクラスを定義します。これは、別の属性heightを追加し、そのresize関数をオーバーライドしてsuper.resizeを呼び出し、指定された係数で高さをサイズ変更するためです。

プログラミングの観点からは、Squareには、Rectangleにはないものはありません。 RectangleのサブクラスとしてSquareを作成する意味はありません。

5
Danubian Sailor

LSPによって、2つの間に継承関係を作成し、setWidthsetHeightをオーバーライドして正方形が同じになるようにすると、混乱して直感的でない動作が発生するためです。コードがあるとしましょう:

Rectangle r = createRectangle(); // create rectangle or square here
r.setWidth(10);
r.setHeight(20);
print(r.getWidth()); // expect to print 10
print(r.getHeight()); // expect to print 20

しかし、メソッドcreateRectangleSquareを返した場合、これは、SquareRectangeを継承することで可能になるためです。その後、期待は崩れます。ここで、このコードでは、幅または高さを設定すると、それぞれ幅または高さのみが変更されることが予想されます。 OOP=のポイントは、スーパークラスを操作するときに、その下のサブクラスについての知識がまったくないということです。サブクラスが動作を変更して、スーパークラスに関する期待に反する場合は、バグが発生する可能性が高く、そのようなバグはデバッグも修正も困難です。

OOP=についての主要なアイデアの1つは、それが動作であり、継承されるデータではないということです(これもIMOの主要な誤解の1つです)。そして、正方形と長方形を見ると、継承関係に関連する可能性のある行動自体はありません。

4
Euphoric

LSPが言うのは、Rectangleから継承するものはすべてRectangleでなければならないということです。つまり、Rectangleが行うことをすべて実行する必要があります。

おそらく、Rectangleのドキュメンテーションは、Rectangleという名前のrの動作は次のようになると書かれています。

r.setWidth(10);
r.setHeight(20);
print(r.getWidth());  // prints 10

Squareに同じ動作がない場合、Rectangleのように動作しません。そのため、LSPはRectangleから継承してはならないと言っています。言語はこのルールを強制できません。メソッドのオーバーライドで何か間違ったことをやめることはできませんが、「言語がメソッドをオーバーライドできるので問題ない」ということは、それを行うための説得力のある引数です!

さて、上記のコードが10を出力することを意味しないようにRectangleのドキュメントを書くのはpossibleです。この場合、おそらくSquareRectangleの可能性があります。 「これはXを実行します。さらに、このクラスの実装はYを実行します」のようなドキュメントが表示される場合があります。もしそうなら、あなたはクラスからインターフェースを抽出し、インターフェースが保証するものとそれに加えてクラスが保証するものとを区別するための良いケースがあります。しかし、人々が「変更可能な正方形は変更可能な長方形ではなく、不変の正方形は不変の長方形である」と言うとき、彼らは基本的に上記が実際に変更可能な長方形の合理的な定義の一部であると想定しています。

2
Steve Jessop

サブタイプ、および拡張により、OOプログラミングは、多くの場合、リスコフ置換原理に依存します。A<= Bであれば、Bが必要な場所でタイプAの任意の値を使用できます。これはかなりOOアーキテクチャの公理の多く。つまり、すべてのサブクラスがこのプロパティを持つと想定されます(そうでない場合、サブタイプはバグがあり、修正する必要があります)。

しかし、この原則はほとんどのコードの非現実的/非代表的であるか、または(重要なケースでは)満足することが実際には不可能であることがわかります!この問題は、四角形-四角形問題または円-楕円問題( http://en.wikipedia.org/wiki/Circle-ellipse_problem )として知られています。満たす。

couldは、ますます観測的に同等の正方形と長方形を実装していることに注意してください。ただし、区別が役に立たなくなるまで、より多くの機能を捨てることによってのみです。

例として、 http://okmij.org/ftp/Computation/Subtyping/ を参照してください

1
Warbo