したがって、私たちはおそらく 四角形から継承する正方形 を含むリスコフ置換問題のほとんどの教科書で提供されている例に精通しているでしょう。このアプローチの反対点は、正方形は数学的な意味で「is a」の長方形ですが、親クラスである長方形は正方形で簡単に説明できないため、このモデルはSOLIDプログラミング—リスコフの代替原理。
私の質問は、正方形の「is a」長方形から長方形の「is a」正方形への依存関係を逆にすることは理にかなっていますか?明らかに、数学的な設定ではこれは誤りです。しかし、私たちがOOPを使用する理由の大部分は、書き込みまたはコピー貼り付けする必要がある、準一意のコードの量を場所から場所へ減らすことです。それは、これらの2つの構造は理にかなっていますが、問題の古典的なステートメントで見られる親子関係は明らかに異なります。
私はソフトウェア工学のコースを終えたばかりで、この点が明確に説明されていたとは思いません。 2つのクラス間に「is a」関係があるからといって、必ずしも抽象化が必要であるとは限らないことは知っていますが、2つのクラス間に接続があることは、数学的な意味で満足できるようです。
また、私は、サイドプロジェクトとして、小さなゲームエンジンに取り組んでおり、それらの間のある種の抽象化が理にかなっている可能性があることに注意してください。
LSPに違反せずに四角形の代わりにできない正方形の典型的な例は、ちょっとした「トリックの質問」であり、洗練されています。
問題は、融合のために発生します...つまり、長方形のimplementationは、実際には長方形ではありません。
幅と高さを個別に設定できることは、長方形の固有のプロパティではありません。長方形は、幅と高さが不変であっても長方形です。
したがって、特定の長方形について、その寸法の制約(または制約の欠如)について何も想定してはいけません。数学的な四角形は、それだけで、それ以上でもそれ以下でもないし、数学的な正方形IS実際には、数学的な長方形の特定の完全な例です。
ただし独立して調整可能な幅と高さを持つことが保証されている長方形(通常、クラスを有用にするために動作をコーディングするため)は、実際には長方形ではありません...それは何か別のものです。これは、特定の追加保証された動作に加えて、長方形です。
したがって、この例は、長方形でも正方形でもない新しいthingsを作成するために発生しますが、-mis-labelsそれらは「長方形」と「正方形」であり、「whoa!here's LSPに違反する完全なサブセットの奇妙なケース!」まあそれは全くそうではありません。長方形でも正方形でもない新しいものが作成されました。
OPの質問に直接答えるために、長方形を一種の正方形と見なすことは、数学的にもコンピューターサイエンスにおいても意味がありません。四角形から四角形を継承する理由はわかりません。そのアーキテクチャでメソッドを作成しても、それらの単語が通常私たちにとって意味することへの類似性/自然なマッピングはすぐになくなります。
Liskovの置換原則は、プログラムモジュールがBaseクラスを使用している場合、プログラムモジュールの機能に影響を与えることなく、Baseクラスへの参照をDerivedクラスに置き換えることができると述べています。
したがって:
Square
はRectangle
から継承できません。
次の関数を想像してみてください。RobertC. Martinの恥ずかしげなくアジャイルソフトウェア開発、115ページからコピーThe Real Problem:
_void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.Area() == 20);
}
_
以下のいくつかの段落で本を引用すると便利です。
問題は関数
g
にあると主張するかもしれません-作者は幅と高さが独立していると仮定する権利を持っていませんでした。g
の作者は同意しません。関数g
は、引数としてRectangle
を取ります。Rectangle
という名前のクラスに明らかに適用される不変条件、真実の声明があり、それらの不変条件の1つは、高さと幅が独立していることです。g
の作者は、この不変条件を主張するあらゆる権利を有していました。不変量に違反したのはSquare
の作者です。
実際、長方形クラスに関連付けられた一連のコントラクトを想像できます。これらのコントラクトは、C#やJavaなどのコードで記述したり、EiffelやSpec#などのパブリックインターフェイスの一部にしたり、コメントの形式でのみ想定または記述したりできます。
契約の1つはRobert C. Martinによって提案され、Rectangle::SetWidth(double w)
の事後条件で構成されています。
_assert((itsWidth == w) && (itsHeight == old.itsHeight));
_
_itsHeight == old.itsHeight
_はSquare
を返すため、false
はこの事後条件に違反しています。
Rectangle
はSquare
から継承できません。
ここでは、同じロジックが適用されます。 Rectangle
がSquare
から継承することを想像してください。関数g(Square& s)
は、たとえば2を乗算することによって、正方形の幅を変更します。正方形の定義を前提として、表面積に4を掛けると仮定するのが賢明です。
ただし、新しい表面積は4倍ではなく2倍になるため、Rectangle
クラスのインスタンスをg
に渡すと、この仮定に違反します。ここでも同じロジックが適用され、Rectangle
からSquare
を継承することも同様に悪いことを意味します。
数学的特性ではなく、オブジェクト指向設計が動作をモデル化します。
つまり、簡単な答えは、正方形は長方形のように振る舞うことができず、長方形は正方形のように振る舞うことができないため、どちらの場合も親子関係はないということです。
より長い答えは、正方形は特定の領域でのみ長方形の特別な形であり、それは幾何学的特性です。 OODは異なるドメインを扱い、相互作用するオブジェクトの動作をモデル化します。そのため、このドメインに関係がない場合は無視してください。システムの動作に実際には無関係な関係をモデル化しようとすると、システムで大きな問題が発生します。システムの動作に関係する関係に固執します。
Rectangle
はSquare
から継承しないでください。正方形は長方形の特別な形です(すべての辺の長さが等しい)。したがって、一般的な形式はRectangle
であり、Square
は特殊化です。したがって、Square
からRectangle
への汎化を描くことができますが、その逆はできません。
リスコフ置換について:制約を導入すると、落とし穴を回避できます。間違いなく「ただ受け継ぐ」ことは魅力的ですが、もちろんあなたはまだあなたの脳を持っています。そして、それは円/楕円の継承の背後にある問題を明らかにしました。したがって、自分が何をしているかを認識し、自明ではないことに注意する必要があります。そして:独断的プログラミングは必ずしも良いプログラミングではありません。
height/width
は保護されており、変更するにはセッターメソッドが必要です。そして、Square
の制約により、高さと幅の両方が等しくなければなりません。現在、Square
にはさまざまな実装がある可能性があります。 setHeight
は副作用として幅を変更します(これは良い考えではないようです)。または、そうするときに例外を発生させます。これはオーバーライドで実装できます。便宜上、setSize
でのみ使用できる高さ/幅の単一の整数でSquare
を追加できます。この設計は、さまざまな方法で同様に拡張できます。
どちらにしても、継承はまったく適用されません。すべての正方形は長方形ですが、長方形には何も追加されません。それは単に長方形のピリオドであり、長方形を拡張しません。継承は、子孫が何らかの方法で基本クラスを拡張する(期待される)場合を除き、無意味です。
混乱は、完全に恣意的な人間のラベル「square」から生じます。人々が長辺が短辺の長さの2倍の長さの四角形をdoubieと呼び、Wordが英語の辞書にある場合、stackexchangeでdoubiesと四角形について同じ質問が表示されます。
同じことが円と楕円にも当てはまります。楕円はタイプであり、円は単なる一般的な化身です。