Spiga

“九种不够面向对象的对象“的在实际项目中的合理运用

2009-07-04 19:59 by Ivony..., 2414 visits, 收藏, 编辑

本文可以视为对http://www.cnblogs.com/DesignPatterns/archive/2009/07/04/nine-non-oo-o.html文中观点的反面意见。

 

在引用的这篇文章中,作者指出了九个其认为“不够面向对象”的对象。而本人一直不喜欢不是面向对象或者不够面向对象这样的提法,在本人看来,所有的这些对象不过是因为其不够饱满,而在实际的项目设计中,根本没有必要要求所有的对象都是饱满的。对象的饱满程度与对象的设计粒度有关系,简单的说,如果是一个非常简单的项目,比如说输入一个数输出某种算法的结果这样的小练习,一个对象负责交互、计算、计时等等工作是没有任何问题的。但是如果你是做一个计算器,那么你把这些东西都写在一个类型里面,粒度就明显过大了。

仍然用生物界来做例子,将对象比喻成细胞,那么对于原生生物而言,只需要一个细胞(一个对象)就够了,这个细胞可以自行增殖、吸取养分,甚至可以抵御外敌和自由行动。但是对于高级生物而言,则需要大量的细胞协同工作,这些细胞可以是没有行为的(骨骼细胞),也可以是不能增殖的(脂肪细胞),但这些都不影响它们在存在的合理性。

 

 

废话不多说,下面随便指出几个所谓的不够面向对象在设计中的合理运用:

 

1、贫血对象,事实上就是退化成数据结构的对象,对象可以视为是数据结构的扩展,对象=数据+行为。如果一个数据自己没有行为可不可以呢?没有道理不可以的,比如说狗会叫,石头会叫么?石头难道就不是对象了?没有行为的对象最著名的如string,另外如Uri,所有的基元类型也可以视为是所谓的贫血对象。不要以为这样的对象就与OO没有关系,没有石头用什么去打狗呢?说设计是不是合理,取决于这个东西是不是丢失了本来应该有的行为,而不是说没有行为的都不是Object了。

2、管理者对象,其实就是抛弃了数据结构只剩下方法退化成方法组的对象。同上面的对象的存在是合理的一样,只有方法组的对象存在也是合理的。而且非常重要,因为方法是多态的。绞肉机需要自己有肉么?刀子需要自己有苹果么?那么方法组为什么一定要带上数据呢?比如说我们定义一个刀子类,显然这个刀子是不需要自己有苹果的,没有人会new 刀( 一个苹果 )吧?而只可能是:一把刀.切( 一个苹果 )。而且,我们可以方便的class 花刀 : 刀,利用多态的特性来使得我们可以把苹果切成星型、三角形等各种形状。你能说这不是OO么?

3、储柜对象,事实上是一个强类型的Dictionary,这种对象存在的意义与Dictionary存在的意义是一样的(或者说就是1的情况)。既可以作为数据打包的传递,也可以作为一个适配器来对某个抽象的、不存在的东西进行控制。作为Dictionary的用途就不说了,说说适配器的用途。比如说DOM模型中的对象,大部分对象的属性都是R/W的,这样的对象实际上是文档模型的一个适配器,通过修改对象来修改文档。这又有何不可呢?

4、多管闲事的对象,虽然这大多数情况下是抽象的问题,但在一个优秀合理的设计中这种情况也并非不存在。就那篇文章的作者所举的例子而言,完全属于抽象的问题,Pet怎么可能都会CatchRat?这是很滑稽的。但一个类型将继承的方法屏蔽这种情况却“不都是”抽象的问题。比如说我们设计一个类:猫,那么猫一定会吃东西、能跑跳。但是残疾的猫和昏迷的猫呢?那么它们不是猫么?显然它们也是猫,但是并不是所有猫都具备的行为它们都有,这样的情形,就必须在派生类中屏蔽基类方法来实现。而不可能是我们将猫去继承于残疾猫。继承关系的最大原则是抽象关系,具体类型继承抽象类型。其他原则是帮助我们把握抽象关系的方法,而不是必须遵循的原则。

最后关于第8点谈谈,没有专属自己的数据,没有专属自己的行为,就不要一个类型这样的说法显然是不靠谱的,是不是需要一个类型,最重要的是看这个类型有没有意义,而不是这个类型有没有独特、专有的东西。类型的首先在于类型本身而不在于其成员。或者说,正确的设计是先确定类型,然后再设计其成员,而不是将成员列张表然后归纳出几个类型。比如说我们设计一个正方形类,继承于矩形,请问这个正方形是有什么它有而矩形没有的呢?但是这个类型是有意义的,比如说我们有些方法只能对正方形进行操作,就不应该接收一个矩形对象,然后判断这是不是一个正方形。

Add your comment

52 条回复

  1. #1楼 横刀天笑      2009-07-04 20:52
    其实你引用的文章的作者也并没有表明我们的设计中就不能存在这些“不够面向对象的对象”,我觉得原文挺好,大可以把这个作为一个参考,不一定必须遵循。如果设计中出现这些对象,我们留意一下即可,至于是否需要避免,那就是“设计”的问题,设计就是权衡。
    这里引用《敏捷软件开发 原则、模式与实践》里的一句话:“接受缺陷而不是去追求完美这是一个工程上的权衡问题。好的工程师知道何时接受缺陷比追求完美更有利。”
     回复 引用 查看   
  2. #2楼 gihelo      2009-07-04 20:59
    支持,赞同。对象设计实际就是灵活适度,保留变化。应该是以一种“运用之妙,存乎一心”的态度来看待他,而不应该让自己陷在那些条条框框里面。
     回复 引用 查看   
  3. #3楼 横刀天笑      2009-07-04 21:06
    @gihelo
    就像你的评论里说的,灵活适度。那这个“度”是什么?我是否可以理解为度量?那如何去度量?也许有一种声音在喊着:设计是一门艺术,不可度量,只求“运用之妙,存乎一心”,但是对这种是“艺术”的东西我一直不感冒,说实话,“艺术”有点让人“迷茫”,看不到也摸不着,那我们就需要一些原则或是”条条框框“来辅助我们,不让我们的思绪游离在太虚之间。这些原则给我们指明方向。
     回复 引用 查看   
  4. #4楼 3个fwc[未注册用户]2009-07-04 21:13
    打酱油去了,lz我顶你,coding是为人服务的,人不能被coding搞死
     回复 引用   
  5. #5楼 徐少侠      2009-07-04 21:41
    楼主太偏激了点

    string是有方法的

    刀子去操作苹果,但是如果苹果的所有属性都封装良好,那么其实不是刀子导致苹果的属性改变,而是苹果自身的属性方法来实现修改的。
    万一是个钝刀子,苹果的属性方法根本就拒绝他对自身的修改操作

    我刺你一刀,你死了。但是不是因为我的操作,而是你自身有死掉这个方法

    一个对象,如果自身没有Dispose,那么任何外部的方法都无法让他消亡了。
    简单说,一个人如果是不会死,没有死这个方法,我砍他1万刀,碎成10万片,他还是不会死的

    字典同样是有方法的,如果没有这些方法,谁会去用字典?
    橱柜如果没有方法,就不是一个好橱柜

    残疾的猫和昏迷的猫,他们一定是继承了跳和吃这两个方法的
    问题是由于一些原因,他们在执行这两个方法的时候。要么是无法将充足的能量传递到驱动装置上,要么是-=了对外部事件的关联
    反正残疾的猫在事件刺激时候是有响应的,但是跳跃方法的执行输出和正常猫不一样
    晕迷的猫么对任何事件都不响应了

    一个Enable属性是false的按钮难道不是从button类继承来的?这时候似乎并不需要必须在派生类中屏蔽基类方法来实现

    最后正方形可以有一个单参数的构造,而矩形是不可能有的




     回复 引用 查看   
  6. #6楼 jyk2009-07-04 22:16
    大家为什么总是强调“特例”,而忽视“一般”呢?

    通吃的方法是没有的,但是人们总是不自觉的用“通吃”的标准去衡量。
     回复 引用   
  7. #7楼 KenBlove      2009-07-04 22:26
    运用之妙,在乎一心。
     回复 引用 查看   
  8. #8楼[楼主] Ivony...      2009-07-04 22:26
    首先回应横刀天笑。显然我认为这里根本就不是什么权衡的问题。

    权衡是指,我应该怎样,但迫于复杂度和其他情况,我不能这样。即存在于一个更好的设计,但因为种种原因而不能选择。但这里所列举的任何一种情况都不存在一种更好的设计,而是本应该就该如此设计,如同石头不可能有行为但石头的存在合理性是毋庸置疑的一样。一个有行为的石头(可以自己飞起来砸人)并不是一个更好的设计。


    继续来回应徐少侠。

    1、毋庸置疑的string是有方法的,如同object都是有方法的一样,这并不能说明什么问题,显然原文作者并非指没有任何方法的对象才是贫血对象。如果一定要拿String来说事,那就看看int、Type或者是枚举类型吧。显然没有人会认为Math的那些方法都应该写到int里面去。

    2、刀子切苹果是刀子改变苹果的属性这样的理解是错误的,应该是创建了两个新的对象,嗯,两个苹果块。人能不能被杀死应该是由刀子决定的,就像把能不能东西装到箱子里并不是由被装的东西决定一样。没有Dispose方法就不能消亡的说法更是站不住脚的,别忘了在GC出现之前直接delete就可以把个对象从宇宙中抹去,而不需要征求这个对象自己的意见。而GC在干掉一个对象的时候是不会询问它的感受的。就像发电厂不会征询你程序的意见就会给你家断电一样。

    其他的略。。。。
     回复 引用 查看   
  9. #9楼[楼主] Ivony...      2009-07-04 22:51
    @横刀天笑
    继续回应横刀天笑

    一个规则或者原则,其存在的特例太多的时候,我们就应该去反思这个规则是不是本身有问题。我承认原文指出的几种情况,有他一定的道理,但是将这些情况归纳成所谓的“不够面向对象的对象”则极容易对大家造成误导。就像每个运动员都想要金牌一样,谁会希望自己设计的对象是“不够面向对象的对象”呢?

    在设计思想上,我认为指导原则和实例比条条框框要重要的多。高内聚松耦合就是一个很好的指导,但很可惜原文的作者似乎错误的理解了这个原则。高内聚低耦合是面对模块的设计而言的,而不是针对类型的的,这个原则普遍的适用于方法、类型等等其他的任何一切。至于一个模块到底是一个方法、一个类型、还是一堆类型,这是不重要的。没有必要要求每个类型都是高内聚低耦合的。怎样让类型相互协作成为一个内聚的模块,怎样增加类型来解除模块之间的耦合,才是设计所要考虑的问题。
     回复 引用 查看   
  10. #10楼 横刀天笑      2009-07-04 23:05
    关于 刀子砍人的问题
    你凭什么说一个人”死“了?我想”死“也许是一个人的一个状态(属性),或者像游戏里面的用”血量“表示,那么活人变成各死人,或血量的降低,我觉得应该是”人“的职责,不是”刀子“的职责。
     回复 引用 查看   
  11. #11楼 横刀天笑      2009-07-04 23:13
    @Ivony...
    谁会希望自己的设计的对象是”不够面向对象的对象“,这就需要度量了。不够面向对象的对象没有什么不好,没有人说它不好。

    请看原文的前一句:
    1.我们虽然列出了这九种情况,但并不是说出现了下面的情况就一定有问题了;我们希望读者这可以将其作为一种信号——仔细考虑一下是不是有更好的设计。
    就像
     回复 引用 查看   
  12. #12楼[楼主] Ivony...      2009-07-04 23:17
    关于刀子砍人的问题实在是有够无聊,在OO中其实是这样表示的:

    Corpse Kill( Human human )
    {
    //...
    }

    即这个方法接受一个人,返回一个尸体。

    我知道大家一定会说,那这个人对象还在啊,这不符合逻辑。但实际上这很符合逻辑,因为刀子只管把人变成尸体,而这个人的什么社会关系、注销户口、分配遗产绝对不是刀子管的事情。在这一刻刀子已经完成了自己的任务,把人变成了尸体。
     回复 引用 查看   
  13. #13楼 横刀天笑      2009-07-04 23:19
    再次讨论”刀子砍人“
    比如游戏里面有各种个样的Role,有小虾米,小怪兽,打怪兽,Boss。你有一把刀(刀是固定的),如果”刀子能不能杀死是由刀子决定的“,那么,是否你用这刀子砍小虾米,小怪兽,打怪兽,Boss,它们要不都死,要不都不死呢?
    或者你在刀子的砍方法里这样写:
    砍(Role f)
    {
    if(f.Type=="小虾米"){
    //死掉了
    }else if(f.Type == "小怪兽"){
    //丢血50%
    }
    //.......
    }
    要这样的设计么?
     回复 引用 查看   
  14. #14楼 Galactica      2009-07-04 23:21
    引用徐少侠:楼主太偏激了点

    string是有方法的

    刀子去操作苹果,但是如果苹果的所有属性都封装良好,那么其实不是刀子导致苹果的属性改变,而是苹果自身的属性方法来实现修改的。
    万一是个钝刀子,苹果的属性方法根本就拒绝他对自身的修改操作

    我刺你一刀,你死了。但是不是因为我的操作,而是你自身有死掉这个方法

    一个对象,如果自身没有Dispose,那么任何外部的方法都无法让他消亡了。
    简单说,一个人如果是不会死,没有死这个方法,我砍他1万刀,碎成10万片,他还是不会死的

    字典同样是有方法的,如果没有这些方法,谁会去用字典?
    橱柜如果没有方法,就不是一个好橱柜

    残疾的猫和昏迷的猫,他们一定是继承了跳和吃...


    我就说一点哈,"我刺你一刀,你死了。但是不是因为我的操作,而是你自身有死掉这个方法",按照你的逻辑,人虽然有"死掉"这个方法,但是在你描述的场景"我刺你一刀"中,人是不会无缘无故的自己调用自己"死掉"的方法,而是收到"被刺中"的消息后才自己调用自己的"死掉"的方法(这里省去了"被刺中"到"死掉"之间的其它机体内部方法调用).

    那问题就来了,从宏观上看,"被刀刺中",那么人得接收"被刺中"的消息,那这个消息就是刀,人之间传递的对象,这个对象本身只负责传递消息,那么本着面向对象"职责单一"的原则,这个对象就不应该有其它的行为,因为它只需要把"刺中"的信息传递给人就足够了.

    所以,我觉得"贫血模型"在项目中的运用是非常OO的.
     回复 引用 查看   
  15. #15楼[楼主] Ivony...      2009-07-04 23:23
    引用横刀天笑:@Ivony...
    谁会希望自己的设计的对象是”不够面向对象的对象“,这就需要度量了。不够面向对象的对象没有什么不好,没有人说它不好。

    请看原文的前一句:
    1.我们虽然列出了这九种情况,但并不是说出现了下面的情况就一定有问题了;我们希望读者这可以将其作为一种信号——仔细考虑一下是不是有更好的设计。
    就像



    这样的说法是很难让人信服的,是啊,“不够面向对象的对象”不一定是有问题的设计,但不会有人认为他是优秀的设计吧?而我写这篇文章的意义在于告诉大家,所谓的这九种情况,都有可能就是最优秀的设计。而不仅仅只是不一定有问题,

    而且,让大家在遇到这种情况的时候,仔细考虑一下是不是有更好的设计,这句话其实是暗示大家,“仔细考虑一下”多半“会有更好的设计”(相信大多数人都会这样理解吧)。而在很多情况下,那根本就不存在不是么?
     回复 引用 查看   
  16. #16楼[楼主] Ivony...      2009-07-04 23:39
    给杀人的问题做一个结论吧。

    我们讨论刀子和人的关系的时候,应当摒弃具体的场景,如果我们是在设计一个游戏,那么当然这中间的情况有所不同。但如果这样假设的话,我们能得出无数种可能性,而这个问题的具体细化则可以从经典物理学一直拆到量子力学。如同:
    刀子为什么能杀人?
    因为
    1、刀子锋利能刺入人体内。
    2、刺入人体内会导致失血而死亡。

    为什么锋利就能刺入体内?
    因为锋利的刀子在皮肤上形成更大的压强,超过皮肤和组织的承受能力。
    ……………………

    所以在讨论刀子杀人的问题上,我们不妨将其抽象成两个没有具体的对象,我们不要去考究刀子和人的具体实现。那么这个问题是很显然的,人会不会被刀子捅死,取决于刀子是不是捅了人而不是人捅了刀子。从这个角度来出发,我们的程序应该写成knife.Kill( human ),而不是human.ToDead( knife )。这个应该没有异议吧。

    那么很显然的,这个方法是Knife的,换言之,Knife能不能Kill human,或者能Kill什么东西,是Knife的事情,当然Knife可以去征询human,比如说你有多强壮?你有没有穿软猬甲、金缕玉衣啥的?然后来决定把human变成个wounded还是corpse。

    当然,如果有人还是坚持认为在游戏里面应该是人被刀子捅这样来表述,我也赞同,但显然我们也能举出更多的表述为刀子捅人更好的场景来。
     回复 引用 查看   
  17. #17楼[楼主] Ivony...      2009-07-04 23:53
    至于一定要追究人死了必然是人发生了改变,我就举一个人不用任何改变的范例:

    public murdor( Human human, Knife knife )
    {
    var corpse = knife.Kill( human );
    house.Remove( human );
    house.Add( corpse );
    }

    然后一个邻居在观察这个房子的动静:

    neighbor.OnNeighboringHouseHappen += house.Happen;

    他看到了:
    public void OnNeighboringHouseHappen ( object sender, HouseHappenEventArgs e )
    {
    if ( e.Happen == "Human Removed" )//一个人不见了
    {
    House.Telephone.Alarm();
    }
    }
     回复 引用 查看   
  18. #18楼 Galactica      2009-07-04 23:55
    引用横刀天笑:<FIELDSET class=comment_quote><LEGEND>引用</LEGEND>Ivony...:&lt;fieldset class="comment_quote"&gt;&lt;legend&gt;引用&lt;/legend&gt;横刀天笑:@Ivony...
    谁会希望自己的设计的对象是”不够面向对象的对象“,这就需要度量了。不够面向对象的对象没有什么不好,没有人说它不好。

    请看原文的前一句:
    1.我们虽然列出了这九种情况,但并不是说出现了下面的情况就一定有问题了;我们希望读者这可以将其作为一种信号——仔细考虑一下是不是有更好的设计。
    就像&lt;/fieldset&g...


    我觉得不是说什么除了行为就没有别的属性了,或者除了属性就没有别的行为了,任何对象都有属性和行为,只是我们在具体的应用场景中,没有必要必须把那些我们不需要的行为和属性非要实现在对象上.

    "用进废退"用在"重构"上是很有道理的,很多对象的属性和行为在多次"重构"后就"消退"了.
     回复 引用 查看   
  19. #19楼[楼主] Ivony...      2009-07-04 23:58
    真无聊,,,,

    对于你说的什么刀子不能自己去杀人,就像石头不能自己跳起来砸人一样。我同意,但别忘了不是我说的刀子杀人。我所针对的是人不必事先知道刀子的存在就能被杀。

    如果一个问题这么绕来绕去的话,别浪费彼此珍贵的时间,我也没兴趣说多话。
     回复 引用 查看   
  20. #20楼 勇敢的鸵鸟      2009-07-05 00:01
    你的讨论完全脱离了原文中特别指出的:
    “我们这里所说的面向对象的对象特指领域对象,即对象中包含领域数据和业务逻辑。”我还用了“特指”这两个字。
    这篇文章我写了1周多,请五六个人审稿,修改了多次。麻烦下次“随便指出”问题时好好看看原文。
     回复 引用 查看   
  21. #21楼[楼主] Ivony...      2009-07-05 00:06
    恕我愚昧,啥叫作领域对象?如果对象中包含领域数据和业务逻辑,则所谓的什么贫血对象从何而来?不是包含业务逻辑么?管理者对象又怎么冒出来的?不是包含领域数据么?

    或者您可以划个道道,在设计什么系统的时候,就一定不会出现这些问题?

    别忘了如果你的道道没划清楚,用一个所谓的领域对象来搪塞是不能推卸误导的责任的。
     回复 引用 查看   
  22. #22楼 横刀天笑      2009-07-05 00:07
    好吧,那我就把注意力放到你举的那几个例子吧。
    1:原文作者指的是领域对象。Domain Object或者称称为Business object,我的理解就是应该包含商业逻辑的对象。一个领域对象包含逻辑还是好一点的(.net里的属性Property里包含的逻辑也算)。石头不会叫就没有行为了?你后面还说了要用石头打狗呢?那打是不是石头的行为,如果不是,那不跟你的”刀砍人“说法相矛盾。
    2:没有人说绞肉机需要肉,刀子需要苹果,再说刀子除了苹果以外难道就没别的属性了?作者说的管理者对象不是你理解的那样,作者说的是像微软的企业库里冒出来的那个SqlHelper类,这是一个辅助类。
    3:这里你又忘记了作者说的是领域对象,而且貌似你也没理解作者说的橱柜对象是什么类型的对象。比如你现在有一个类,这个类里有一个Hashtable,你难道把这个Hashtable直接暴露出去?当然是提供一些方法对这个Hashtable操作,而不是直接暴露啊。
    4:这一条作者举的这个例子确实不好,很明显的捉老鼠不应该抽象出来,但有的时候真的有一些职责一开始很难鉴别到底是谁的。


    本来评论我自己删了,后来想想还是发出来吧,权当作讨论。
     回复 引用 查看   
  23. #23楼 斯克迪亚      2009-07-05 00:07
    public class 石头
    {
    public int 尺寸{get;set;}
    public bool 绊人(人 倒霉蛋)
    {
    ……
    return 倒霉蛋.动作==动作.倒地;
    }
    }
     回复 引用 查看   
  24. #24楼 勇敢的鸵鸟      2009-07-05 00:08
    google "domain object", "business logic"
     回复 引用 查看   
  25. #25楼[楼主] Ivony...      2009-07-05 00:12
    继而,如果你一定要强调领域对象,为什么标题是“不够面向对象的对象”,而不是“不够面向对象的领域对象的设计”?实非我吹毛求疵。
     回复 引用 查看   
  26. #26楼 thinklose2[未注册用户]2009-07-05 00:18
    按照"勇敢的鸵鸟"的"9种不够"去完成你讲的"做一个计算器"这个实践, 我完全不觉得"粒度过大"...你和我得出的这个判断是个人好恶.

    以生物细胞为例, 无意识地站在了这样的角度: 把细胞设计成一个类. 试问: 细胞的自行繁殖, 吸取养分等等是不是行为呢? 即使是"自行"繁殖, 也是一种行为嘛! 骨骼细胞也有行为, 比如说"造血", "生长", 生长算不算行为?
    class Cell{
    weight = 5mg;
    }
    请问如果没有写任何方法或者操作, 这个cell.weight不会改变, 它不会生长

    如果要拿string来讲也不对. string本身不是一个对象(按照万物皆对象的观念来看, 它是没有行为的对象)! 它实际是一个primitive data:元数据, 在运行时会产生一个 System.String 对象进行wrap!

    string foo = "a" + "b";

    在这一句中, "+" 连接这个行为是System.String实现的. 这个说法在.net中成立, 在javascript中也是基本一致的;

    "刀和苹果", 假设用贫血方法的设计Apple, 如下面所示(省略类定义)

    void foo(){
    Apple a = new Apple();
    Knife k = new Knife();

    // get apple apart.
    k.Cut(a);
    }

    显然foo()不够内聚.


    "9种不够"并没有说要抛弃管理类. 按照"9种不够"我得出的理解是: 如果一定要把"切"这个行为划分到苹果类中去, 可以这样写

    class Apple{
    void GetApart(Knife k){
    // 也可在构造的时候传入刀子, 但是那样做不符合思维逻辑.
    k.Cut(this);
    }
    }

    // 我在理解上换了一个角度, 因为把"切"这个行为放在苹果自身不符合思维和语言逻辑, 理解为"get parts of an apple"比较顺一点.

    class Knife{
    void Cut(Apple a){
    Console.Write("Knife cut " + a);
    }
    }
    ...
    void foo(){
    Apple a = new Apple();
    a.GetApart(new Knife());
    }


    下面这个按博主打的比方写出来的.
    class Knife{
    Apple _apple;

    Knife(Apple a){
    this._apple = a;
    }

    public void Cut(){
    Console.Write("Knife cut " + _apple);
    }
    }
    ...
    void foo(){
    Knife k = new Knife(new Apple());
    k.Cut();
    }

    有趣的是第二种设计和第三种是一样内聚的! 不好理解的是第三种设计, 因为十分别扭, 它是按照博主打的比方写的.
     回复 引用   
  27. #27楼[楼主] Ivony...      2009-07-05 00:21
    @横刀天笑
    1、领域对象是不能说明问题的。对象就是对象,设计就是设计,不是加了几个华丽的辞藻就能改变什么的。我们就谈实际的例子,我们设计一个进销存系统,请问“订单”这个对象拥有什么行为?设计一个OA,请问文档(公文)这个对象有什么行为?还是不应该设计订单、公文这样的对象。
    至于石头与刀矛盾,我说最后一次,刀砍人不是我提出来的,我举的例子是刀切苹果,如果你一定要认为刀自己是不能自动的切苹果的不妨想象成一个开着的榨汁机。在讨论石头时,我所说明的这个对象没有行为。讨论刀子时,说明这个对象没有数据。在讨论刀杀人时,说明的是刀杀人不需要人作改变。把这三个问题混为一谈是无聊的。

    2、苹果不是刀的属性,在这个例子里,刀是一个没有数据只有行为的东西,即原文中的所谓管理者对象。

    3、原文中的橱柜对象无非是想说明,一个对象不应该只是Dictionary。存进去啥,取出来啥,没有行为和规则。或者说想说明对象应该有规则(不能修改摇尾巴)。

    不再回复。
     回复 引用 查看   
  28. #28楼 斯克迪亚      2009-07-05 00:27
    引用横刀天笑:关于 刀子砍人的问题
    你凭什么说一个人”死“了?我想”死“也许是一个人的一个状态(属性),或者像游戏里面的用”血量“表示,那么活人变成各死人,或血量的降低,我觉得应该是”人“的职责,不是”刀子“的职责。

    如果说你有什么职责造成你自己死亡,那就是:自杀()。
    你的生命是你本身应有的属性,它不是专为刀子设立的,很多事物都可能造成你的死亡,比如汽车、疾病,这是属性,不是行为。
    刀子也不是专门为切人设计的吧,刀子也能切别的什么,他们是两个明确的对象,这没错。
    不过刀子也可以有自己的属性,而非纯粹的管理器,比如锐度、尺寸、形状都对其伤害有影响。
    但这也不代表每个设计里都必须要有这些东西,你不可能把一个东西的所有属性都靠代码描述出来,你的程序也不可能都用上这些属性,如果你的程序需要的仅仅是一把刀子,不需要考虑那些属性,那么做成管理器模式有何不可吗?
    就比如阅卷人这一对象,完全可以就只有审阅、打分方法,没有其他诸如性别、学历、专业、性格等等复杂到乱七八糟的属性,你以为你是上帝吗?
     回复 引用 查看   
  29. #29楼[楼主] Ivony...      2009-07-05 00:28
    @thinklose2
    比如说我们定义一个刀子类,显然这个刀子是不需要自己有苹果的,没有人会new 刀( 一个苹果 )吧?而只可能是:一把刀.切( 一个苹果 )。

    显然我的方案是:
    knife.Cut( apple );

    而不是那三种方案的任何一种。

    apple.GetApart不是高内聚的设计,反之,是一种紧耦合的设计,因为被切碎本来就不是苹果的行为,如果按照这种设计思想。Apple的方法可以有成千上万个,例如被吃被烤被种被包装被出售被榨汁,,,,,
     回复 引用 查看   
  30. #30楼 斯克迪亚      2009-07-05 00:30
    引用横刀天笑:再次讨论”刀子砍人“
    比如游戏里面有各种个样的Role,有小虾米,小怪兽,打怪兽,Boss。你有一把刀(刀是固定的),如果”刀子能不能杀死是由刀子决定的“,那么,是否你用这刀子砍小虾米,小怪兽,打怪兽,Boss,它们要不都死,要不都不死呢?
    或者你在刀子的砍方法里这样写:
    砍(Role f)
    {
    if(f.Type=="小虾米"){
    //死掉了
    }else if(f.Type == "小怪兽"){
    //丢血50%
    }
    //.......
    }
    要这样的设计么?

    public void 攻击(物体 目标物体)
    {
    物体.生命值-=30;
    }
    有你那么笨的设计?
     回复 引用 查看   
  31. #31楼 斯克迪亚      2009-07-05 00:43
    引用Ivony...:至于一定要追究人死了必然是人发生了改变,我就举一个人不用任何改变的范例:

    public murdor( Human human, Knife knife )
    {
    var corpse = knife.Kill( human );
    house.Remove( human );
    house.Add( corpse );
    }

    然后一个邻居在观察这个房子的动静:

    neighbor.OnNeighboringHouseHappen += house.Happen;

    他看到了:
    public void OnNeighboringHouseHappen ( object sender, HouseHappenEventArgs e )
    {
    if ( e.Happen == "Human Removed" )//一个人不见了
    {
    House.Telephone.Alarm();
    }
    }

    我觉得死人也是人啊,应该算作人的一种状态属性而已,比如:bool Alive;
    火化后就彻底释放该对象了~
    单独做出尸体类有点怪,莫非要实现魔兽世界那种尸体与灵魂分离?
    算了,再讨论就跑哲学层面上了~
     回复 引用 查看   
  32. #32楼 勇敢的鸵鸟      2009-07-05 01:22
    引用Ivony...:给杀人的问题做一个结论吧。

    我们讨论刀子和人的关系的时候,应当摒弃具体的场景,如果我们是在设计一个游戏,那么当然这中间的情况有所不同。但如果这样假设的话,我们能得出无数种可能性,而这个问题的具体细化则可以从经典物理学一直拆到量子力学。如同:
    刀子为什么能杀人?
    因为
    1、刀子锋利能刺入人体内。
    2、刺入人体内会导致失血而死亡。

    为什么锋利就能刺入体内?
    因为锋利的刀子在皮肤上形成更大的压强,超过皮肤和组织的承受能力。
    ……………………

    所以在讨论刀子杀人的问题上,我们不妨将其抽象成两个没有具体的对象,我们不要去考究刀子和人的具体实现。那么这个问题是很显然的,人会不会被刀子捅死,取决于刀子...

    @Ivony...
    你这个结论实在是非常的扯淡,讨论对象和对象之间的关系是怎么可能“应当摒弃具体的背景”呢?在不同的背景下可以knife.kill(person)也可以person.killedBy(knife),甚至可以将这个动作分到knife和person两个对象中实现。
    试想不同的人被不同的刀子杀死,这是典型的Bridge模式的运用场景。不同的刀子实现不同的kill行为,不同的人实现不同的呼喊/流血/倒地等行为。这个要视应用背景不同而定的。而且,职责和行为的迁移时设计演变过程中最重要的活动。
    既然你不了解什么是领域对象,我就原谅你了,但是在自己理解之前不要说别人涉嫌误导,因为你这样做就涉嫌误导了。关于技术的讨论欢迎继续。
     回复 引用 查看   
  33. #33楼[楼主] Ivony...      2009-07-05 02:48
    老实说我也实在是无聊,一个刀子捅人的问题,从哪个角度来分析都是正确的,我并没否认人被刀子捅死这样的表述方式,甚至是人想死用刀子这都可以接受,但这些都是毫无意义的,我们所要讨论的是,刀子捅死人需不需要人“能死”,换言之人是不是必须声明自己能不能被刀子捅死,如果人不声明自己能被刀子捅死就成了耶和华。

    显然这是一个荒谬的问题,我早就已经进行了阐述。然后,某些人又举出各种场景说明设计应该是“人被刀子捅”而不是“刀子捅人”。当然,你非要设计成人被刀子捅,那么人死不死当然是人这个类自己来处理更为合理。但是没有任何理由说明我们必须设计成“人被刀子捅”,这是这个论点的致命缺陷,所以我才说结束这个无聊的讨论。

    其实说来说去,就是说即使在你所说的场景下,人被刀子捅是更合理的设计,也不能否认刀子去捅人同样是合理的设计,也就无法说明人必须为“刀子捅这”个行为做些什么,否则不会“死掉”。


    摒弃具体的场景是一种抽象的手段,相信园子里绝大多数人都有办法将刀子和人两个对象孤立出来看待问题,而不是一定要说明我们是在做网游,我们是在做CS。

    是否我也必须加入这种无聊的讨论举出另一个场景说明刀子捅人也是合理的设计并且的的确确完全取决于刀子而不需要人“可以死掉”。将讨论引入无穷尽的无聊和口水呢?

    不知道为什么有些人特别喜欢执着于无聊的问题。


    本来那篇文章就够无聊的,这边的口水仗就更无聊了。。。。
     回复 引用 查看   
  34. #34楼 新余论坛[未注册用户]2009-07-05 04:36
    我也顶你 评价的好
     回复 引用   
  35. #35楼 winter-cn      2009-07-05 04:41
    首先 楼主你偷换了概念 "不够面向对象"跟"不是良好设计" 是两回事 虽然维持程序统一风格非常重要 但并不意味着不够面向对象的地方就不是好设计

    string是个非常标准的值的概念 你用它来作为贫血对象合理应用的例子 是完全错误的

    对于贫血对象和管理者对象 违背了对象必须具有的三个特性(状态,行为,标识,参考Grandy Booch《面向对象分析与设计》)之中的两个 所以它们不是完全的面向对象。

    然后就是人和刀子的问题, 楼上各位的讨论似乎偏离了程序设计而转到其他方面.

    从对象设计的角度看,对象应该是一组数据和操作它们的方法,刀子状态没有改变,那么这个方法就没有成为刀子方法的必要。人的状态改变了,但是想想就可以知道,人不可能知道世间万物对自己会造成什么影响,人只需要提供一个操作自己状态的函数hurt就可以了,然后由调用者来做刀子捅人,人受伤这个动作。

    从现实中来看刀子捅人这个动作,显然不是刀子发出的 也不是被捅的人发出的,那么这个动作必然不可能是刀子或者人的行为,所以这个应该是另一个对象的方法,这跟上面的分析完全一致。
     回复 引用 查看   
  36. #36楼 winter-cn      2009-07-05 04:44
    不同的人被不同的刀子杀死,这是典型的Bridge模式的运用场景。
    -------------------------------------------------------------------
    提醒下 这个跟bridge没有关系
     回复 引用 查看   
  37. #37楼 路人戊[未注册用户]2009-07-05 07:59
    对象的行为可分为主动和被动,主动就是对象发出这个动作,被动就是对象的状态被外部改变。但无论那种,必须注意的是如果这个行为不能引起对象自身状态的变化,则此时应该仔细考虑这个行为是不是属于这个对象。其实领域模型设计从某种层面上来说就是斟酌行为到底属于哪个对象。

    SQLHelper这种例子,它不属于逻辑层,在DDD中它属于服务层。因此讨论SQLHelper的设计没什么太多意义。
     回复 引用   
  38. #38楼 横刀天笑      2009-07-05 08:29
    昨晚太热,喝了点酒,话多了,今天看起来有的话也跑题了~~~Sorry。
    既然LZ说无聊,俺就不继续了,呵呵
     回复 引用 查看   
  39. #39楼 John.geng[未注册用户]2009-07-05 09:24
    你们真够无聊的。

    像石头,刀子这些东西本身就不具备行为,根本就是个 中间媒体,它们只是被利用而已,至于谁利用,被利用干了什么都不是它们自己做决定的。因此不存在石头砸人,刀杀人,一说,也不存在人被石头砸,被刀杀了。而应该说:某人被另一人用石头砸了,某人被另一人用刀杀了。 石头与刀只是中间物,没有自己的行为只是做传递,只是被利用。
     回复 引用   
  40. #40楼[楼主] Ivony...      2009-07-05 11:02
    引用winter-cn:
    首先 楼主你偷换了概念 "不够面向对象"跟"不是良好设计" 是两回事 虽然维持程序统一风格非常重要 但并不意味着不够面向对象的地方就不是好设计

    string是个非常标准的值的概念 你用它来作为贫血对象合理应用的例子 是完全错误的

    对于贫血对象和管理者对象 违背了对象必须具有的三个特性(状态,行为,标识,参考Grandy Booch《面向对象分析与设计》)之中的两个 所以它们不是完全的面向对象。



    1、在评论中我已经说的很清楚,不论原文的出发点是什么,不够面向对象的对象都足以传递这是个不好的设计的信息。何况,我这篇文章所表述的就是这些不够面向对象的对象就是“面向对象”的最佳设计,不是或不够面向对象的。

    2、对象没有什么必须有的三个特性,没有任何道理说缺失了“任何特性”的对象就是所谓的“不够面向对象的对象”。值也是对象的一种,正如同没有任何方法需要把所有的表达式都写上一遍一样,一个类型也没有必要把所有的特性都包含一次。再回到最开始的问题,所谓的贫血对象如string具备状态和标识,所谓的管理者对象具备行为和标识。无论从哪一方面来说,这都是荒谬而无聊的讨论。
     回复 引用 查看   
  41. #41楼 pwy[未注册用户]2009-07-05 12:53
    我要疯了
     回复 引用   
  42. #42楼 徐少侠      2009-07-05 15:56
    贫血或者充血

    各自有自身比较合适的施展空间

    这世界上把一切对象都搞成贫血或充血的都是不对的

    通过上面大家的讨论,其实已经有了一些基本答案
    例如
    在仅仅作为消息传递的时候,使用贫血对象作为消息参数是很合理的。
    想想平时一直在用的那个EventArgs
    其实刀子砍人,砍的位置可以成为事件参数对象来传递

    在一些具体的领域对象身上,搞成贫血的就不好。
     回复 引用 查看   
  43. #43楼[楼主] Ivony...      2009-07-05 21:50
    我在找让博客园不会把这些无聊的东西发到我的邮箱的功能。
     回复 引用 查看   
  44. #44楼 winter-cn      2009-07-06 05:17
    引用Ivony...:
    1、在评论中我已经说的很清楚,不论原文的出发点是什么,不够面向对象的对象都足以传递这是个不好的设计的信息。何况,我这篇文...

    "值也是对象的一种"
    "对象没有什么必须有的三个特性"
    "无论从哪一方面来说,这都是荒谬而无聊的讨论"
    这些结论都是哪来的?楼主如果讨论问题只是不停提出观点,那真是有够无聊。

    你如果想重新定义一下面向对象 那我无话可说 但是请你先把定义给出来 否则某些人(如我)就会把你说的面向对象当作一般概念中的面向对象。
     回复 引用 查看   
  45. #45楼 szwe      2009-07-06 10:48
    如果对象本身不具备多态和继承一类的特性,是否将其设计成对象根本就是无所谓的吧?面向对象设计不是为了代码重用才存在的么?
     回复 引用 查看   
  46. #46楼[楼主] Ivony...      2009-07-06 13:08
    引用winter-cn:
    引用Ivony...:
    1、在评论中我已经说的很清楚,不论原文的出发点是什么,不够面向对象的对象都足以传递这是个不好的设计的信息。何况,我这篇文...

    "值也是对象的一种"
    "对象没有什么必须有的三个特性"
    "无论从哪一方面来说,这都是荒谬而无聊的讨论"
    这些结论都是哪来的?楼主如果讨论问题只是不停提出观点,那真是有够无聊。

    你如果想重新定义一下面向对象 那我无话可说 但是请你先把定义给出来 否则某些人(如我)就会把你说的面向对象当作一般概念中的面向对象。



    就我看来,这个评论恐怕才是最无聊的。

    首先并不是我提出观点,而是某人抛出来的对象必须有的三个特性这样的观点,而我只是予以反驳。所以这种程度的颠倒是非实在有够可笑。就算要说我不停的提出观点,岂不也是有更无聊的人在不断的提出无聊的观点,譬如阁下?

    来这里的无聊的人有一个共同点,就是都有无限的自信相信自己掌握了真理,并提出各种佐证“某专家文献”、“某团队审核”、“某权威杂志”。可唯一缺失的就是事实。

    老实说将话题引入无聊的方向,如杀人问题的争论,还算是设计思想之间的碰撞能擦出火花的话,像诸如这样的无聊人士的话,请自觉屏蔽,而没有必要去查阅资料在犄角旮旯之处找到只言片语的什么所谓佐证,当然,除非你真的很无聊。
     回复 引用 查看   
  47. #47楼 月照孤周      2009-07-06 17:05
    winter-cn:"人只需要提供一个操作自己状态的函数hurt就可以了,然后由调用者来做刀子捅人,人受伤这个动作"
    同意这个观点,因为将人被刀杀或刀杀人的方法写在人或刀的对象里,都是紧耦合的,
    也同意LZ的部份观点,我们以为领域模型就是稳定的,不易变的,但实际情况是,一个对象,将来会对它进行怎样的操作并不能知道,领域不同,它的操作也不同,公司业务变化,它的操作也会变化,为什么非要将数据与操作封装在一起呢,所以存在就是道理,所以hibernate是贫血模型+manager,所以SOA将SDO与SCA分开
     回复 引用 查看   
  48. #48楼 徐少侠      2009-07-06 21:59
    @月照孤周
    我觉得Hibernate之所以选择贫血,因为他本质不是要提供领域对象,而是提供对数据表的映射,并且映射的结果依然是业务数据而不是业务对象。
    只是用对象的方式来向业务层呈现数据而已。

    因为一个通用ORM系统是不可能理解数据表的实际业务含义的。
    所以,自动映射得到的类,其实也仅仅是最基本的DataRow而已了

    所以我认为,过分吧Hibernate里面映射得到的类当作领域类来处理是不合理的。

    其实Hibernate的功能也仅仅是DataSet+Linq查询了。
    拿他当领域是看高他了


     回复 引用 查看   
  49. #49楼 Frank Wang      2009-07-06 22:33
    引用我刺你一刀,你死了。但是不是因为我的操作,而是你自身有死掉这个方法

    刺一刀致死.和自己"死掉"(寻短见)完全是2个死法..
    我又想到那句话了"摇狗尾巴,狗摇尾巴" 呵呵..
     回复 引用 查看   
  50. #50楼 Frank Wang      2009-07-07 00:38
    引用John.geng:
    你们真够无聊的。

    像石头,刀子这些东西本身就不具备行为,根本就是个 中间媒体,它们只是被利用而已,至于谁利用,被利用干了什么都不是它们自己做决定的。因此不存在石头砸人,刀杀人,一说,也不存在人被石头砸,被刀杀了。而应该说:某人被另一人用石头砸了,某人被另一人用刀杀了。 石头与刀只是中间物,没有自己的行为只是做传递,只是被利用。

    说石头.刀子自身没有行为的..其实那都是特定的场景..
    石头可以风化.刀可以生锈..谁说他们自身没行为了? 如果你说一个游戏里这2个东西不具备行为还可以,那如果是一个"刀子生锈模拟系统"呢?
     回复 引用 查看   
  51. #51楼 Frank Wang      2009-07-07 07:26
    引用横刀天笑:
    好吧,那我就把注意力放到你举的那几个例子吧。
    1:原文作者指的是领域对象。Domain Object或者称称为Business object,我的理解就是应该包含商业逻辑的对象。一个领域对象包含逻辑还是好一点的(.net里的属性Property里包含的逻辑也算)。石头不会叫就没有行为了?你后面还说了要用石头打狗呢?那打是不是石头的行为,如果不是,那不跟你的”刀砍人“说法相矛盾。
    2:没有人说绞肉机需要肉,刀子需要苹果,再说刀子除了苹果以外难道就没别的属性了?作者说的管理者对象不是你理解的那样,作者说的是像微软的企业库里冒出来的那个SqlHelper类,这是一个辅助类。
    3:这里你又...

    Business Object
    这里的 business翻译成 商业? 我英语是不好,可也不能这么逗我吧?
    垃圾译本看多了?

    引用业务对象的分类

    1.实体业务对象:
    表达了一个人,地点,事物或者概念.根据业务中的名词从业务域中提取的.如客户,订单,物品.在EJB应用程序中,一般为实体Bean.在传统的web应用程序中,可能是包含业务应用的状态和行为的普通javabean.
    2.过程业务对象:
    表达应用程序中业务处理过程或者工作流程任务.通常依赖于实体业务对象,是业务的动词.在EJB应用程序中,通常是模型的会话bean,或者消息驱动bean.在非EJB应用中,可能是javabean,包含特定的行为,作为应用程序的管理者或者控制者.
    3.事件业务对象:
    表达应用程序中由于系统的一些操作造成或产生的一些事件.
     回复 引用 查看   
  52. #52楼 浪子      2011-01-07 15:26
    模型是一种简化,它对现实进行阐述,只是抽象出与解决手头问题有关的方面,而忽略掉无关的细节问题。 不限定一个领域的边界,去讨论刀和人的问题,就变成万金油了,咋整都行。
     回复 引用 查看