円と楕円の問題は、関係を逆にすることで解決できますか?


13

持つCircle拡張Ellipse休憩にリスコフSubstition原理、すなわち、あなたはXとYが独立して楕円を描くように設定することができますが、Xは、常に円のためにYに等しくなければなりません:それは事後条件を変更するため、。

しかし、ここでの問題は、Circleを楕円のサブタイプにすることによって引き起こされたのではないでしょうか?関係を逆転させることはできませんか?

ですから、Circleはスーパータイプです-メソッドは1つだけですsetRadius

次に、setXとを追加して、楕円が円を拡張しsetYます。setRadiusEllipseを呼び出すと、XとYの両方が設定されます。つまり、setRadiusの事後条件は維持されますが、拡張インターフェースを使用してXとYを個別に設定できるようになりました。


1
最初にウィキペディアを調べましたか(en.wikipedia.org/wiki/Circle-ellipse_problem)?
Doc Brown

1
はい-私も...私の質問でそれをリンクする
HorusKol

6
そして、この正確なポイントはその記事でカバーされているので、私はあなたが何を求めているのか分かりませんか?
フィリップケンドール

6
「一部の著者は、楕円が追加機能を持つ円であるという理由で、円と楕円の関係を逆にすることを提案しています。残念ながら、楕円は円の不変式の多くを満たすことができません。提供することもできます。」
フィリップケンドール

3
この問題の前提条件が最も明確であることがわかったのは、ウィキペディアの記事の一番下にあるen.wikipedia.org/wiki/…です。状況に依存し、いくつかのきれいなデザインがありますが、それはこれら2つのクラスから何が必要かに依存やるしない、こと
アーサーハヴリチェク

回答:


37

しかし、ここでの問題は、Circleを楕円のサブタイプにすることによって引き起こされたのではないでしょうか?関係を逆転させることはできませんか?

この問題(および正方形/長方形の問題)は、1つのドメイン(ジオメトリ)の関係が別のドメイン(動作)で保持されていると誤って仮定しています。

幾何学理論のプリズムを通して見ている場合、円と楕円は関連しています。しかし、それはあなたが見ることができる唯一のドメインではありません。

オブジェクト指向設計では、動作を扱います

オブジェクトの定義特性は、オブジェクトが担当する動作です。そして、振る舞いの領域では、円と楕円の振る舞いが非常に異なるため、それらをまったく関連していると考えない方が良いでしょう。このドメインでは、楕円と円には重要な関係はありません。

ここでの教訓は、OODに最も意味のあるドメインを選択することです。別のドメインに存在するという理由だけで関係を試してみることではありません。

この間違いの最も一般的な実世界の例は、動作が非常に異なっていても類似したデータを持っているため、オブジェクトが関連している(または同じクラスである)と仮定することです。これは、データの行き先を定義して「データを最初に」オブジェクトの構築を開始するときの一般的な問題です。最終的に、まったく異なる動作をするデータを介して関連するクラスを作成できます。たとえば、給与明細と従業員オブジェクトの両方に「総給与」属性が含まれている場合でも、従業員は給与明細のタイプではなく、給与明細は従業員のタイプではありません。


(アプリケーション)ドメインの懸念とOODの行動および責任能力を分離することは非常に重要なポイントです。たとえば、描画アプリケーションでは、円を正方形に変形できるはずですが、ほとんどの言語のクラス/オブジェクトを使用して簡単にモデル化することはできません(通常、オブジェクトはクラスを変更できないため)。そのため、アプリケーションドメインは、特定のOOP言語の継承階層に常にうまくマッピングされるとは限らず、強制的にしようとするべきではありません。多くの場合、構成が優れています。
エリックエイド

3
この答えは、問題全体について私が見た中で最も良いものであり、より一般的な場合に設計ミスの可能性がどのように発生する可能性があるかです。おかげで
HorusKol

1
@ErikEidtオブジェクトが動作を変更する問題は、分解によってOODで解決できます。たとえば、モーフィング可能な形状が円に変わった場合、クラスを変更する必要はありません。代わりに、クラスは、モーフィング時に別の動作に交換できる現在の幾何学的動作オブジェクトを取ります。この他のクラスには、現在モデル化されている幾何学形状の規則が含まれており、モーフィング可能な形状クラスは、幾何学振る舞いのためにこのクラスに従います。オブジェクトが別のクラスに変形した場合、動作クラスを別のものに変更します。
コーマックマルホール

2
@Cormac、そう!一般的には、私が述べたように、構成の形式と呼びますが、より具体的には戦略パターンなどを特定できます。本質的に、モーフィングされないアイデンティティと、その後変更できるその他のものがあります。全体として、アプリケーションドメインの概念の違い、および特定の言語のOOPの詳細、およびそれらの間のマッピングの必要性(アーキテクチャ、設計、プログラミングなど)を明確に強調しています。
エリックエイド16

1
しかし、仕事は給料になることができます。

8

円は楕円の特殊なケースです。つまり、省略記号の両方の軸が同じです。問題領域(幾何学)では、楕円が一種の円である可能性があると述べることは基本的に間違っています。この欠陥モデルを使用すると、円の多くの保証に違反します。たとえば、「円上のすべてのポイントは中心までの距離が同じです」。それも、リスコフ代替原則の違反になります。楕円の半径はどのようになりますか?(しかしsetRadius()、もっと重要なことではありませんgetRadius()

楕円のサブタイプとしての円のモデリングは根本的に間違っていませんが、このモデルを壊すのは可変性の導入です。メソッドsetX()setY()メソッドがなければ、LSP違反はありません。異なる次元のオブジェクトが必要な場合、新しいインスタンスを作成するのがより良い解決策です。

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}

1
大丈夫-だから、Ellipseとの間に共通のインターフェースがあればCircle(などgetArea)、タイプに抽象化されます-LSPからサブタイプShapeできEllipse、LSPを満たすことができますか?CircleShape
HorusKol

1
@HorusKolはい。どちらも実際に正しく実装するインターフェイスを継承する2つのクラスは完全に問題ありません。
Ixrec 16

6

最初から「楕円」クラスと「サークル」クラスを持ち、一方が他方のサブクラスであると主張するのは間違いです。2つの現実的な選択肢があります。1つは、個別のクラスを持つことです。それらには、色、オブジェクトが塗りつぶされているかどうか、描画の線幅などの共通のスーパークラスがあります。

もう1つは、「Ellipse」という名前の1つのクラスのみを持つことです。そのクラスがある場合、それを使用して円を表すのは簡単です(実装の詳細に応じてトラップがある可能性があります。楕円には角度があり、その角度の計算は円形の楕円では問題にならないはずです)。円形楕円に特化したメソッドを使用することもできますが、これらの「円形楕円」は完全な「楕円」オブジェクトのままです。


クラスEllipseの特定のオブジェクトが実際に両方の軸が同じであるかどうかを確認するIsCircleメソッドがあります。角度の問題も指摘しました。円は「回転」できません。

6

Cormacには本当に素晴らしい答えがありますが、そもそも混乱の理由について少し詳しく説明したいと思います。

オブジェクト指向の継承は、「リンゴとオレンジはどちらも果物のサブクラスである」などの現実世界のメタファーを使用して教えられることがよくあります。残念ながら、これはオブジェクト指向の型はプログラムとは独立して存在するいくつかの分類階層に従ってモデル化されるべきであるという誤った信念につながります。

ただし、ソフトウェア設計では、アプリケーションの要件に従って型をモデル化する必要があります。通常、他のドメインの分類は無関係です。「Apple」および「Orange」オブジェクト(スーパーマーケットの在庫管理システムなど)を使用する実際のアプリケーションでは、これらはおそらくまったく別個のクラスではなく、「Fruit」などのカテゴリはスーパータイプではなく属性になります。

楕円の問題は赤いニシンです。ジオメトリでは、円は楕円の特殊化ですが、例のクラスは幾何学的な数字ではありません。重要なのは、幾何学的図形は変更できないことです。ただし、それらは変換できますが、円省略記号に変換できます。そのため、円は半径を変更できるが省略記号は変更できないモデルは、ジオメトリに対応していません。このようなモデルは特定のアプリケーション(描画ツールなど)で意味をなす場合がありますが、クラス階層の設計方法には幾何学的分類は関係ありません。

だから、CircleはEllipseのサブクラスである必要がありますか?これらのオブジェクトを使用する特定のアプリケーションの要件に完全に依存します。描画アプリケーションには、円と楕円の処理方法にさまざまな選択肢があります。

  1. 円と楕円を異なるUIを持つ異なるタイプの形状として扱います(たとえば、省略記号に2つのサイズ変更ハンドル、円に1つのハンドル)。これは、アプリケーションの観点から、幾何学的に円であるが円ではない楕円を使用できることを意味します。

  2. 円を含むすべての楕円を同じように扱いますが、xとyを同じ値に「ロック」するオプションがあります。

  3. 楕円は、スケーリング変換が適用された単なる円です。

可能な設計ごとに異なるオブジェクトモデルが作成されます-

最初のケースでは、CircleとEllipsesは兄弟クラスになります

2番目のクラスでは、明確なCircleクラスはまったくありません。

3番目のクラスでは、明確なEllipseクラスはありません。そのため、いわゆる円楕円問題は、これらのいずれにも写り込みません。

それで、提起された質問に答えるために:円は楕円を拡張するべきですか?答えは次のとおりです。何をしたいかによって異なります。しかし、おそらくそうではありません。


1
非常に良い答えです!
Utsav T

3

LSPポイントに続いて、この問題に対する「適切な」解決策の1つは、@ HorusKolと@Ixrecが登場したことです。両方のタイプをShapeから導き出します。しかし、それはあなたが作業しているモデルに依存しているので、常にそれに戻るべきです。

私が教えられたのは:

サブタイプがスーパータイプと同じ動作を実行できない場合、関係はIS-A前提に保持されません。変更する必要があります。

  • サブタイプは、スーパータイプのスーパーセットです。
  • スーパータイプは、サブタイプのサブセットです。

英語で:

  • 派生型は、基本型のスーパーセットです。
  • ベースタイプは、派生タイプのサブセットです。

(例:

  • 不良少年の排気のある車はまだ車です(いくつかによると)。
  • エンジン、車輪、ステアリングラック、ドライブトレインがなく、シェルだけが残っている車は、「車」ではなく、単なるシェルです。)

これが、分類の仕組み(つまり、動物の世界)、そして原則としてオブジェクト指向です。

これを継承とポリモーフィズム(常に一緒に記述される)の定義として使用すると、この原則が破られた場合、モデル化しようとしている型を再考する必要があります。

@HorusKulと@Ixrecで述べたように、数学では型を明確に定義しています。しかし、数学では、円は楕円のサブセットであるため、楕円です。しかし、OOPでは、これは継承の仕組みではありません。クラスは、既存のクラスのスーパーセット(拡張)である場合にのみ継承する必要があります。つまり、すべてのコンテキストで基本クラスのままです。

それに基づいて、私は解決策を少し言い換えるべきだと思います。

シェイプのベースタイプを持ち、次にRoundedShapeを使用します(事実上円ですが、ここでは意図的に別の名前を使用しています...)

...そして楕円。

そのように:

  • RoundedShapeはShapeです。
  • EllipseはRoundedShapeです。

(これは今では言語の人々にとって理にかなっています。私たちはすでに明確に定義された「円」の概念を頭の中に持っています。


私たちの明確に定義された概念は、実際に常にうまくいくとは限りません。

-1

オブジェクト指向の観点からは、楕円は円を拡張しますが、いくつかのプロパティを追加することで円を拡張します。円の既存のプロパティは楕円のままですが、より複雑で具体的になります。この場合、Cormacのように動作に問題はありません。形状には動作がありません。唯一の問題は、言語学的または数学的な意味で、「楕円は円である」と言うのが正しいとは感じないことです。言及されていないが暗黙的である演習のポイントは、幾何学的形状を分類することでした。これは円と楕円をピアと見なし、継承によってリンクしないで、それらが偶然同じプロパティの一部を持っていることを受け入れ、ねじれたオブジェクト指向の心をその観察に邪魔させないことを正当化する理由かもしれません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.