我来讨论一下纯理论的老问题.长方形和正方形的继承关系.

类的定义中,大家一直争吵不休的就是正方形和长方形的关系.所说,正方形不是长方形的子类,因为它违反了长方形的行为.

然而计算机世界应当是当前世界在计算机中的映射.那么我们来考察长方形和正方形的定义,长方形也就是矩形:为四个内角相等的平行四边形。正方形,是平行四边形;. 有一个角是直角;. 有一组邻边相等。

可是oo对象中,却不能是继承关系。为什么?因为里面的概念已经被偷换了,我们所定义的只是我们在某方面运用到的长方形和正方形。它的定义是全新的,包括了里面所有的数据和所有的方法。不再是我们在数学中定义的长方形和正方形。

也就是说我们的oo定义的类,和现实世界不能完全映射上,只是反映其中我们想要得部分。它的定义是不完整的,过于简单,无法反映数据和数据间的关系,契约太过强硬,只要是所用到的行为子类都要遵守。于是我们很着急,这违反了oo初衷。哲学上告诉我们片面的抽象扩大化,会走到我们所期望的反面。可以说这就是一个例子,原本现实分类中很明显的继承关系,反而到了oo中,不成立了,或者说成为一个问题。不象现实中那样清晰。

我们为了简化而把事情搞复杂了。也许我们在纯粹的程序世界里面还可以不大在意这件事,按照自己定义的就可以了。但是我们要做需求分析时,这就成为大问题。那是必须和现实世界映射上的,任何的扭曲都会带来很多的麻烦。现在工作中我们的麻烦确实很多,原本清晰可见的关系,我们要重新调理。于是我们开始思考。

我们要用现实世界中的关系来指导oo设计,于是什么LSP,DBC都冒出来告诉我们怎么做。可是还是不行。

下一篇讨论这个问题在做用例分析时可能造成的泥潭。如何回避。

在我的blog:http://yingyiyy.blog.163.com/有后续文章。讲谈到这在工作中的实际意义。

我不知道谁讨论说正方形不是长方形的子类的
你能把文章给我转贴一下么??
我觉得既然从数学定义上是,实际设计也一定是。
要不然oo的设计就是个错误

我觉得可能是这个问题,当
正方形继承长方形的时候,可以使正方形is a长方形
但是无法保证,x形继承正方形时,使x形既具有正方形的行为又具有长方形的行为

没有明白楼上的意思
x继承了正方形必然具备正方形的特性
所谓继承的意思就是 XX就是XX
男人就是人 ,女人是人
人生孩子是错的
必须是女人生孩子
楼主是说 老女人不具备生孩子的方法么,他具备这个方法,但是返回的东西不同??
可是这在生活中也是必然的
if(age > 60) return null; 或者throw excption(“年纪太大”)
不是这样么??

其实,这在《java编程思想》里面认为是可以成立的。只不过某些涉及父类的方法要修改。
我认为也是成立的。只不过我们从这个争论为什么会发生,会发现类定义里面类太简单,没有涉及到约束的问题。
这是要说的重点。一个事物必然要包括一个约束问题。也就是说一个类要成功映射。不是他的所有属性,方法都必须被遵守,而是只要它符合一个约束,就成立了。

就好像椭圆和圆的关系一样。如果椭圆继承了圆,那么就会产生麻烦。一个圆引用指向一个椭圆的实例。那么给圆半径赋值这个操作到椭圆上面就麻烦了。圆的半径是一样的。椭圆则是不一样的,有一个长的,一个短的。该怎么办?

to siberian
怎么可能那??
这是一个典型的约束啊
由于一个事务的状态改变而行为不同
这时候使用state模式啊
交给他的state模型来做
30岁的女人做什么
60岁的女人做什么
这些不是约束么??

to wlmouse
园是椭圆的子类
因为所有基于椭圆的数学运算都能运用在园上
我们说 园是一种特殊的椭圆 可是不能说椭圆是一种特殊的圆

我的意思是继承是is a的关系如果硬要用继承来描述长方形和正方形
那么实际的关系应该是is a而不是like a(正方形就是长方形吗?)
并且在考虑如果又有一个对象继承了正方形,或者继承了长方形,那他
又是什么,?现实世界不可能有这种情况,但是程序中无法保证,(除非
正方形是final)并且继承保证了当父类能用的地方子类也能用,而真要继承
是需要修改正方形内部逻辑,那么客户端调用的时候就无法保证了(难道父类和子类完全不同?)
我觉得描述正方形和长方形有3中解决方案
1,共同实现一个抽象的图形类或图形接口
2,考虑用组合,而不是继承
3,将正方形定义为final,并且保证其内部逻辑的和理性
且这三种方案的代码耦合度是逐步增加的

其实我个人一直反对用计算机完美的描述现实世界的逻辑,毕竟计算机是01的,太精确了
而现实世界不是这么回事,计算机应该是面向问题的,而不是面向逻辑(其实数学建模本
身也是面向问题的)

Coolyu0916

你明显你弄混了,类的内部问题和类与子类的关联问题.


你说30女,60女那是类的内部的.并没有表达到外面来.
而你建立了30女的类,60女的类的时候.那个约束就变成了类本身的重要的东西.

至于用状态模式也好,不用状态模式也好,不过是设计时的技巧,这些约束还是类定义本身至关重要的东西.不能随便的把它外在了.

你说的三种方法,其实在别处的讨论中已经提到.例如阎宏博士哪里就有一个大坑来讨论这问题.
而我今天说到这个问题是,是从类定义的缺乏约束的明确表达这个角度来的.毕竟清晰,无歧义的映射是oo得终极目标,任何一本讲述oo的书都会提到这一点。
我在blog将提到在实际工作中,这会造成那些问题。

siberian , state模式并不暴露state的
我们使用的统一的方法,给用户也是统一的接口
不过在不同的state下我们引用不同的state类

用户使用的时候肯定是 woman.setAge(30); woman.procreate();
如果不给出age 就采用默认的age,这种接口不对么??
我们平时说女人生孩子,一般来说我们都是认为是年轻的妇女
但是当你告诉他是一个小女孩或者老太太的时候,调用生孩子的方法必然是一个空或者是一个异常(你告诉别人她不能生孩子,或者你跟别人说这是开玩笑)
定义接口的时候你不需要关心这些问题,因为问题只有真的发生才有意义(说女人生孩子没有意义,必须说某个女人生孩子才有意义)
你在考虑问题的时候只要让其具备功能,当你认为某个方法可能存在疑义或者可能出现问题的时候,要在子类或者方法中进行控制。

checkcode 说实话,我一直到现在还不明白你说的意思

我们举例
如果一个对象 x 继承了正方形 比如说 (x为边大于5的正方形)我们称之为大正方吧

你要对图形计算面积,或者周长
那么你有什么不能定义那??
利用长方形的方法就可以了

子类不使用父类的实现不可以么??

toCoolyu0916
咱们在说类之间的关系.这个时候还处在从需求得到分析类的时刻,而没有到后面设计时运用某种技巧,某种模式的时候.
这个时候,那些职责是他本身的.那些职责是外在的.和state还扯不上关系。
比如说,一个女人类他有哪些行为,那些约束。那么state模式只不过是在确定了这个分析类之后的优化处理,希望把女人类某些东西外包了。

那么我们就从需求方面走

首先我们确定了人的行为
我们不可能设计一个万能的persion class
我们只要实现有限的功能,在系统中用到的,比如eat() sleep()
现在要在性别上进行区分
这时候我们应当考虑需要怎样区分了,这个区分是为了标识还是为了实现
比如只是为了表示一个身份,一个证件,而不需要一些女人特定的动作,完全就不必需要子类,或者只是为了添加一些附加功能,也是不需要的。
这个必须从逻辑上说的通,你不能说 persion.procreate(),这个不符合逻辑,用你这个接口的人也会晕的,必须是woman.procreate(),这个时候你说不需要继承,使用组合么??或者是简单的引入一个方法么??

至于具体能不能procreate,需求的时候会告诉你,当女人在20-50的时候可能会生育,但是是20还是50是在运行中才会体现的,不是你设计接口时考虑的问题。那是到了详细设计的时候你才要关心的。


类的约束,我们创建一个Person,那么作为一个顶级类,它可以没有约束,作为一个空泛的类而存在,因为它处在抽象的顶级。
但是我们一旦有了一个子类,并且子类的相对于父类有了约束,比如长方形中长和宽可以随便数值,但是到了正方形中不行了。他们要相等,不相等的就不是正方形。这个子类很明显是靠增加约束来建立的,而不是功能的扩展。
我们在需求中挖掘出子类之后,(注意不是为了方便而创造出来的,而是现实中有了子类),就要确定它是否有了约束,还是只是扩展了。
在你举的例子中,如果生育期妇女这个概念常常出现,那么它就应该是一个类,它成立的约束条件就是年龄在20-50,性别女。不满足这个约束,这个类就不成立。它并不是一个运行时的问题,而是一个类本身的,与生俱来的东西。你可以再后来的设计中,把它放到配置文件中,但是不能回避的是,它是类本身的东西。不符合这个约束,他就不是这个类。
接口设计还要排在分析类获取的后面。至于是否方便也不是考虑的问题。我们在这个阶段是要做到完美的映射。