通过逆转关系可以解决圆椭圆问题吗?


13

具有CircleextendEllipse违反了Liskov替换原理,因为它修改了后置条件:即,可以独立设置X和Y来绘制椭圆,但是对于圆,X必须始终等于Y。

但是,不是因为Circle是Ellipse的子类型引起的问题吗?我们不能扭转这种关系吗?

因此,Circle是超类型-它只有一个方法setRadius

然后,椭圆通过添加setX和扩展Circle setY。调用setRadiusEllipse将同时设置X和Y-意味着保留setRadius的后置条件,但是现在您可以通过扩展接口独立设置X和Y。


1
您是否首先浏览了Wikipedia(en.wikipedia.org/wiki/Circle-ellipse_problem)?
布朗

1
是的-我什至将其链接到我的问题中……
HorusKol 2016年

6
文章涵盖了这一点,所以我不清楚您要问什么?
菲利普·肯德尔

6
“一些作者建议逆转圆和椭圆之间的关系,理由是椭圆是具有附加功能的圆。不幸的是,椭圆不能满足圆的许多不变量;如果圆具有方法半径,椭圆现在将具有同时提供它。”
菲利普·肯德尔

3
我发现关于此问题为何前提不好的最清楚的解释是在Wikipedia文章的最底部:en.wikipedia.org/wiki/…。视情况而定,有几种简洁的设计,但这取决于您需要从这两个类中完成什么,而不是要做
亚瑟·哈维利切克

Answers:


37

但是,不是因为Circle是Ellipse的子类型引起的问题吗?我们不能扭转这种关系吗?

这个问题(以及正方形/矩形问题)是错误地假设一个域(几何)中的关系在另一个域(行为)中成立

如果通过几何理论的棱镜查看圆形和椭圆,则它们是相关的。但这不是您可以查看的唯一领域。

面向对象的设计处理行为

对象的定义特征是对象负责的行为。在行为领域,圆形和椭圆形具有如此不同的行为,最好不要将它们完全相关。在此域中,椭圆和圆没有显着关系。

这里的课程是选择最适合OOD的域,而不是仅仅因为它存在于另一个域中就尝试建立关系。

这个错误的最常见的现实例子是假设对象是相关的(甚至是相同的类),因为即使它们的行为非常不同,它们也具有相似的数据。当您开始通过定义数据的位置来“首先构建数据”对象时,这是一个常见的问题。您可以得到一个类,该类通过行为完全不同的数据相关联。例如,工资单和雇员对象都可能具有“工资总额”属性,但是雇员不是工资单类型,工资单也不是雇员类型。


区分(应用程序)域与OOD的行为和责任能力之间的关注是非常重要的一点。例如,在绘图应用程序中,您也许应该能够将圆变形成正方形,但是使用大多数语言的类/对象很难对此进行建模(因为对象通常无法更改类)。因此,应用程序域并不一定总是很好地映射到给定的OOP语言的继承层次结构,我们不应该尝试强制这样做。在许多情况下,成分会更好。
Erik Eidt

3
到目前为止,这个答案是我所见过的关于整个问题的最好的东西,以及在更一般的情况下如何出现设计错误的可能性。谢谢
HorusKol '16

1
@ErikEidt可以通过分解在OOD中解决对象更改行为的问题。例如,如果可变形形状变为圆形,则不必更改类。取而代之的是,该类采用当前的几何行为对象,可以在变形时将其替换为其他行为。另一个类包含当前正在建模的几何形状的规则,而可变形形状类则针对该类的几何行为。如果对象变形为其他类,则将行为类更改为其他类。
Cormac Mulhall

2
@Cormac,对!通常,我会称其为一种组合形式,尽管您可以更具体地确定战略模式或其他内容。本质上,您拥有一个不变的身份,以及其他可以更改的事物。总而言之,它很好地突出了应用程序域概念之间的区别,给定语言的OOP的细节以及在它们之间进行映射(即体系结构,设计和编程)的需求。
Erik Eidt

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
好-因此,如果存在之间的一些共同的界面EllipseCircle(如getArea),将被抽象为一个类型Shape-能够EllipseCircle分别来自亚型Shape和满足LSP?
HorusKol

1
@HorusKol是的。两个类都继承了它们都确实正确实现的接口,这是完全可以的。
Ixrec '16

7

Cormac有一个非常好的答案,但是我只想首先说明造成混乱的原因。

OO中的继承通常使用现实世界中的隐喻来教授,例如“苹果和橙子都是水果的子类”。不幸的是,这导致人们错误地认为,OO类型应根据独立于程序的某些分类层次结构进行建模。

但是在软件设计中,应根据应用程序的要求对类型进行建模。其他领域中的分类通常是不相关的。在带有“ Apple”和“ Orange”对象的实际应用程序中(例如,超市的库存管理系统),它们可能根本不是完全不同的类,而“ Fruit”之类的类将是属性而不是超类。

圆椭圆问题是一个红色鲱鱼。在几何中,圆是椭圆的特化形式,但是示例中的类不是几何图形。至关重要的是,几何图形是不可变的。虽然可以对其进行变换,但是随后可以将一个圆变换为省略号。因此,圆可以更改半径但不能更改为省略号的模型并不对应于几何。这样的模型在特定的应用程序(例如绘图工具)中可能有意义,但是几何分类与如何设计类层次结构无关。

那么Circle应该是Ellipse的子类,反之亦然吗?这完全取决于使用这些对象的特定应用程序的要求。绘图应用程序在处理圆和椭圆时可以有不同的选择:

  1. 将圆形和椭圆形视为具有不同UI的不同类型的形状(例如,椭圆形上有两个调整大小手柄,圆圈上有一个手柄)。这意味着从应用程序的角度来看,您可以拥有一个在几何上是圆形的椭圆,但不是一个圆形的椭圆。

  2. 将包括圆在内的所有椭圆都一样,但是可以选择将x和y锁定为相同的值。

  3. 椭圆只是已应用缩放变换的圆圈。

每种可能的设计都会导致不同的对象模型-

在第一种情况下,Circle和Ellipses将成为同级

在第二个中,根本不会有单独的Circle类

在第三个中,将没有明显的Ellipse类。因此,所谓的圆椭圆问题不会以任何这些方式进入画面。

因此要回答提出的问题:圆是否应延伸椭圆?答案是:这取决于您要如何处理。但是可能不会。


1
一个很好的答案!
Utsav T

6

从一开始就坚持拥有“椭圆”和“圆”类(其中一个是另一个的子类)是一个错误。您有两种现实的选择:一种是拥有单独的类。它们可能有一个通用的超类,例如颜色,是否填充对象,绘制的线宽等。

另一种是只具有一个名为“椭圆”的类。如果具有该类,则可以很容易地使用它来表示圆形(根据实现的细节可能会有陷阱;椭圆将具有某个角度,并且该角度的计算一定不会对圆形椭圆造成麻烦)。您甚至可以使用特殊的圆形椭圆方法,但是这些“圆形椭圆”仍将是完整的“椭圆”对象。


可能存在IsCircle方法,该方法将检查Ellipse类的特定对象实际上是否具有相同的两个轴。您还指出了角度问题。圆不能“旋转”。

3

遵循LSP点,@ HorusKol和@Ixrec就是解决此问题的一种“正确”解决方案-从Shape派生两种类型。但这取决于您使用的模型,因此您应该始终返回到该模型。

我被教导的是:

如果子类型不能执行与超级类型相同的行为,则该关系在IS-A前提中不成立-应该对其进行更改。

  • 子类型是超类型的SUPERSET。
  • 超级类型是子类型的子集。

用英语讲:

  • 派生类型是基本类型的SUPERSET。
  • 基本类型是派生类型的子集。

(例:

  • 根据某些人的说法,排气不良的汽车仍然是汽车。
  • 没有引擎,车轮,转向齿条,传动系统,只剩下外壳的汽车不是``汽车'',而是外壳。)

这就是分类的工作原理(即在动物界),并且原则上是在OO中。

使用此定义作为继承和多态性的定义(它们总是在一起写的),如果违反了这一原则,则您应该尝试重新考虑要建模的类型。

正如@HorusKul和@Ixrec所提到的,在数学中您已经明确定义了类型。但是在数学中,圆是椭圆,因为它是椭圆的子集。但是在OOP中,继承不是这样工作的。如果一个类是现有类的SUPERSET(扩展),则该类仅应继承-意味着,在所有上下文中它仍是基类。

基于此,我认为解决方案应略作修改。

有一个Shape基本类型,然后有RoundedShape(实际上是一个圆形,但是我在这里使用了不同的名称...)

...然后是椭圆。

那样:

  • RoundedShape是一个形状。
  • 椭圆是RoundedShape。

(现在,这对于使用语言的人来说是有意义的。我们已经在脑海中有了一个清晰定义的“圆圈”概念,而我们通过归纳(汇总)来尝试做的事情打破了这个概念。)


我们明确定义的概念在实践中并不总是可行。

-1

从OO角度来看,椭圆确实会延伸圆,它通过添加一些属性来专门化它。圆的现有属性仍然保持椭圆形,只是变得更加复杂和具体。在这种情况下,我看不到任何行为问题,就像Cormac一样,形状没有行为。唯一的麻烦是,从语言或数学的角度说“椭圆是一个圆”是不对的。因为练习的整个要点(未提及但还是隐含的)是对几何形状进行分类。这可能是一个很好的理由,将圆形和椭圆形视为同级,而不是通过继承将它们链接起来,并接受它们恰好具有某些相同的属性,并且不要让扭曲的OO头脑对这种观察产生影响。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.