颠覆传统-面向对象的设计思想

颠覆传统-面向对象的设计思想 2008-11-13 来源:cnblogs 颠覆传统-面向对象的设计思想(序章) 从我们最初接触面向对象思想的时候,我想我们接触到的第一个概念应该就是“类”,我们一直在讨论诸如如何设计类、如何实现类等高深的问题,但是我们有没有思索过到底什么叫做“类”,类的本质是什么?。按照大多数的面向对象的书籍中的介绍来看,类就是一个数据结构,封装了数据和操作,对于这样的答案,我估计大家都不会满意。 那到底什么是类呢?在讨论这个问题之前,我们先探讨一下类的由来。“类”在英语对应的单词是“Class”,如果大家翻一翻英语词典就可以查到 “Class”的原意是指“种类、把...分类(或分等级)”。Class的概念最早应该是从分类学来的,意思是把对象进行归类(说的可能有些不太准确,欢迎那位高人指正),例如生物学上会根据某一个标准将生物分为动物和植物两大类,然后再根据其它的一些标准将动物又分为鱼类、爬行动物类、两栖动物类等不同的种类,如下图所示: 说到这里,可能大家会欢呼:原来面向对象的类就是分类,太好了!我最擅长这个了!别高兴的太早,谁知道面向对象的分类标准是什么吗?是生物学的标准,还是能不能爬树的标准?不同的标准,导致分类的结果完全不同,如下图所示: 假设现在需要要写一个弹涂鱼的类(又名虾虎鱼,英文名为Goby,一种可以爬上陆地并且会上树的鱼类,据说味道极其鲜美,有海上人参之说) ,怎么写?是不是太容易了,看下面的代码,分分钟就搞定了: 1 '鱼 2 Public Class Fish 3 4 End Class 5 6 '可爬树的鱼 7 Public Class ClimbableFish 8 Inherits Fish 9 10 End Class 11 12 '弹涂鱼 13 Public Class Goby 14 Inherits ClimbableFish 15 16 End Class 打完收功,貌似很完美的解决问题,但是这个时候又添加了一个分类标准,能吃的鱼和不能吃的鱼(鲨鱼能吃,俺吃过,味道不咋地,在这里假设鲨鱼不能吃),又该怎么办,Stupid,再写一个“EatableFish”类不就得了,让可爱的弹涂鱼从可以吃的鱼派生,我最喜欢能吃的鱼了!且慢!动手之前我想搞清楚一个问题:EatableFish从那个类派生?从ClimbableFish类派生?难道可以吃的鱼都是会爬树的鱼?从Fish派生,那么是不是说会爬树的鱼都不能吃?这个时候是不是该咒骂微软为什么不在.NET中支持多重继承?算了,还是转投Java阵营算了。旁边的一位兄弟弱弱的来了一句:好像Java也不支持多重继承吧。怎么办?难道我们就没有办法解决这个问题了吗? 貌似用分类学的搞法搞不定面向对象的类耶,我们错了吗?但是很多教科书上面就是这么说的类的继承是“Is A”(是一个)的关系呀,弹涂鱼是“Is A”能吃的鱼、弹涂鱼“Is A”能爬树的鱼,念起来蛮通顺的嘛。错了!我们都被教科书给误导了!面向对象关注什么?关注的是对象的行为,面向对象是使用行为来对对象进行分类的!在面向对象中派生类为什么能够替换基类(替换原则),不是因为派生类是一个基类,而是因为派生类具有与基类一致的行为,在派生类与基类的行为不一致的情况下派生类仍然是一个基类(如果有人敢否认这个,大家说怎么办?旁边有人喊道:砍死他!),但是这个时候派生类消减了基类的行为,违背了替换原则,这也是恶心设计的由来。所以说,对于面向对象而言我们要关注“Act As”,用“Act As”的标准来对对象进行归类,至于什么“Is A”之类的伪标准统统扔到它姥姥家去。 旁边有人不干了:你跟我说说属性是什么动作!对呀,属性是个什么动作呢?那么请有如此疑问的朋友仔细的考虑一下,是不是可以将属性考虑为GetXXX和 SetXXX的两个方法,至于说字段怎么怎么地的某些兄弟俺就不多说了,回去自个好好想想吧,有些东西是属于开发平台为我们做了很多的工作,只不过我们不知道而已。 好,问题到这里已经有些眉目了,我们该讨论如何使用“Act As”来对对象进行分类了。 高手出招了,代码如下: 1 //可爱的小鱼接口 2 public interface IFish 3 { 4 } 5 6 //可爱的爬树接口 7 public interface IClimbable 8 { 9 } 10 11 //可以吃接口 12 public interface IEatable 13 { 14 } 15 16 //弹涂鱼出场了 17 //我要扮演鱼 18 //我要扮演爬树高手 19 //我要扮演可以吃的美味,貌似没有人愿意扮演这个 20 public class Goby : IFish, IClimbable, IEatable 21 { 22 } 高手!请问我需要怎样表演才能扮演成一条鱼呢?高手愕然。 看来我们的高手还是没有摆脱“Is A”的荼毒呀!我们不要鱼!我们要的是行为!行为由什么决定的呢?由要用你这个类的地方期望要的行为来决定的,例如我需要一个能够提供游泳行为的对象,你就可以抽象"ISwimable"这个动作(这个单词可能不对),然后寻求实现这个动作的对象就可以了(接口倒置原则)。 有些朋友可能会有一些疑问,既然是动作,那么动作之间怎么会有继承呢(接口的继承)?例如: 1 //我是一个数据提供源 2 public interface IDataSource : IDisposable 3 { 4 } 5 仔细想想,这个是继承吗?是“Is A”吗?不是!不知道大家玩过拳皇或者其它的格斗游戏没有,要知道分别连续按键是可以组合出一个大招的,在某些情况下,对象的使用者或者理论一点的说法消费者,需要是对象同时提供上述的两种行为,不过分吧。软件设计的时候往往就是这个地方出问题,如果没有分清楚的话,很有可能把本应该拆分的动作当作一套组合拳给打了(接口隔离原则),这也是混乱的开始,重构的原因。 我们一直以来都从“Is A”的角度来对对象进行归类,但是仔细的想一想,“Is A”的标准是什么?我们怎么样才能判定一个对象“Is A”另外一个对象呢?大家是不是基本靠猜测或者凭经验在做?这也是软件设计一直被当作是一种艺术行为的原因。一下这个图是我的一个观点,请大家参考一下: 其中箭头表示对象的行为,我们关注的行为是指落在系统范围之内的行为,或者系统关心的行为。 好了,今天就写到这里吧,以后有时间我会再详细的讨论如何分拆动作,如何设计类的话题。 颠覆传统-面向对象的设计思想(序章续) 自从《颠覆传统-面向对象的设计思想(序章)》发布出来后看的朋友和评论的朋友很多,有说好的,也有说不好。当然也有很多朋友在文章的评论中发表了自己的见解,在这里我就一些比较典型的评论做一下解释。 * 来自名为wanghualiang 的评论 很佩服楼主的发散型思维。但是远远还没到颠覆传统的地步。 这里谈谈我的观点, 面向对象设计时完全从接口来描述对象本身的特性是不是有问题。 从鱼是不是可吃应该只能作为其一个属性来辨识, Class Fish { public bool IsEatable; } 当客户想吃这条鱼的时候,IsEatable=true;如果是河豚的话就是 False了。 当然有许多种不确定的因素,在可吃不可吃之间。那我们应该 [Flags] Enum Eattype { DeliciousEate,//美味 Distasteful, Barbed, .. } Class Fish { public Eattype eattype; } 我想wanghualiang 的评论代表了相当一部分朋友的看法,从传统的面向对象思想(或者说普遍接受)的观点来看这样做是没有问题的,甚至还被作为了Demo写入了很多的面向对象的教科书。但是实际上这种看法是存在问题的,至少我个人是这么认为的,首先我们需要考虑的问题是:鱼自己能不能决定自己能不能吃,能不能决定自己好不好吃?应该是不能吧,决定鱼能不能吃,好不好吃的应该是吃鱼的对象对吧。也许从普通人的角度来看河豚是不能吃的,但是从高明的大厨或者资深的老饕的角度来看河豚就是无比的美味了,这也是我在序章的最后专门添加一幅图片重点解说对象行为的原因。 话说回来,既然鱼不能决定能不能吃、好不好吃,也就是说明能不能吃的行为不是有鱼能够决定的,那么也许有人会问那为什么要实现IEatable接口呢,和直接做一个属性不是一样吗?这个问题问的非常的好,确实既然鱼不能够决定自己能不能吃、好不好吃,那么为什么鱼要实现IEatable接口呢。其实,在 Fish上实现IEatable接口完全是出于使用方便性和接口的层次(后续的文章中会重点讨论这个问题)来考虑的,完全面向对象的搞法应该是有另外一个对象来鉴定这个鱼是否能吃、好不好吃的(这也是基于设计的平衡来考虑的,可以参看开放-封闭原则)。在这个地方使用接口和属性本质上没有什么差别,但是一旦鉴别鱼能不能吃、好不好吃的鉴别方式(实现方法)发生变化的时候,使用属性的方式就难以扩展了,只能修改代码了,但是使用接口的好处是我可以使用其它的方式补救,例如做一个实现IEatable接口的装饰对象来装饰鱼对象。 另外,导致需要鱼对象实现IEatable接口的原因可能是出于接口隔离原则的考虑,如果使用属性来辨别鱼是否能吃,必然使用的地方就依赖鱼对象了,提高了系统的耦合性。示意代码如下: 1 '检索是否可以吃的食物 2 Public Function GetEatableFish(ByVal foods As IEatable()) As IEatable() 3 4 '用于保存列表 5 Dim tempList As New ArrayList 6 7 '循环的检索 8 For Each item As IEatable In foods 9 10 '判断是否可以吃 11 If item.IsEatable Then 12 13 tempList.Add(item) 14 End If 15 Next 16 17 '返回结果 18 Return tempList.ToArray(GetType(IEatable)) 19 20 End Function 21 22 '是否可以吃接口 23 Public Interface IEatable 24 25 '是否可以吃 26 Function IsEatable() As Boolean 27 28 End Interface 软件设计的时候没有一味的好,也没有一味的差,任何事情都有其两面性,这个是需要取舍的,我们能够做到的事情就是让设计可控,如果设计失控了,那就全部完蛋了。话说回来,如果能够确认当前的系统中评判鱼能不能吃的标准不会发生改变,把这个不会发生改变的东西集成到鱼对象中是完全可以的,在具体的实现上用属性来实现也是一个非常不错的搞法。 * 来自名为 Anders Liu 的评论 嗯。。。建议你在多想想再继续写。 比如这个:public class Goby : IFish, IClimbable, IEatable 我认为这样比较好:public class Goby : Fish, IClimbable, IEatable 看到区别了么? 至于Anders Liu 的评论应该与wanghualiang 的意见基本上是一致的,如果我没有理解错误的话。我们引入一个Class的时候需要考虑的是引入这个Class的目的,如果没有任何目的的引入一个类或者是仅仅简单的为了封装一个方法来引入一个类是不可靠的,后面我会写一篇随笔来专门讨论类和接口。 颠覆传统-面向对象的设计思想(神仙?妖怪?) 在我的前两篇随笔中,我大概的陈述了一下我对面向对象设计的一些想法。本人拙于言,不善表达,写文章陈述观点这种技术活就更不擅长了,如果在文章中有什么 表述不清之处,还请各位海涵。此外,非常欢迎大家对我的文章点评,无论是赞扬、还是批评我都笑纳,多多益善,如果有探讨类型的评论就更好了。 从《颠覆传统-面向对象的设计思想(序章)》和《颠覆传统-面向对象的设计思想(序章续)》的评论来看,对这两篇随笔持不同意见的主要原因是设计和需求之间关系的问题,很多朋友都在他们的评论中一再重申需求的重要性,认为需求和设计密不可分,需求是设计之母,离开需求谈设计都是空谈。 谁也不能说这些观点是错的,但是说这些有用吗?不可否认,谁也不可否认需求的重要性,但是我们在谈论需求的时候,甚至把这个名词当作一个放之四海而皆准的真理的时候,我们有没有仔细的考虑过我们一天到晚谈论的需求到底是个什么东西? 需求是什么,需求就是客户的欲望。人的欲望是无穷的,所以客户的需求永远也是无法满足的。我们做需求的目的是什么?不是满足客户的所有需求,也不可能满足客户的所有需求,而是保证我们能够尽可能的游走在客户勉强接受与暴走之间。说到这里,我不由的想到了“朝三暮四”这个典故,这个典故源于《庄子.齐物论》,说的是有一年碰上粮食欠收,养猴子的人对猴子说:“现在粮食不够了,必须节约点吃。每天早晨吃三颗橡子,晚上吃四颗,怎么样?”这群猴子听了非常生气,吵吵嚷嚷说:“太少了!怎么早晨吃的还没晚上多?”养猴子的人连忙说:“那么每天早晨吃四颗,晚上吃三颗,怎么样?”这群猴子听了都高兴起来,觉得早晨吃的比晚上多了,自己已经胜利了。 是不是很有意思,做需求和养猴子其实也没有什么太大的差别,关键点都是在如何保证自己利益的情况下,取悦客户!这个养猴人实在是一个天才设计师,完全把握住了如何取悦客户这个关键点,至于用户,在取悦了客户的大前提下,用户的利益是可以侵占的(我们做系统的同时就是在损害用户的利益。流程重组,企业再造是用来干什么的,就是用来裁员的)。请记住做需求的关键点是取悦客户,而不是讨好用户。 设计的目的是有以下的两点: * 保证公司的利益 * 取悦客户 作为一个软件设计人员,如果能够同时站在公司的立场和客户的立场,做一个客户和公司都满意的解决方案就是一个非常合格的设计人员了,如果还能够高瞻远瞩的规划产品的远景目标,那么这个设计师绝对可以堪称是个高手。 对于软件设计而言,最难做到的是出于业务而超脱于业务,试想在一个团队中不是所有的组员都能够准确的把握和领会客户需求的,对于大多数开发人员而言能够出色的完成技术问题就是一个非常出色的开发人员了,他们不懂业务是非常正常的一件事情,既懂业务又懂技术的人一般就不会做开发人员了。为了解决开发人员不懂业务的现实,我们有必要为开发人员隔离实际的业务问题,架构设计就是这么一个比较高层次的、掺杂技术问题和业务问题的技术活动,它的主要目的是为大多数开发人员隔离业务,将业务需求翻译成为具体的技术要求。在这个层次的设计谈论什么面向对象的设计是一个过于技术性的话题,至少我是这么认为的,面向对象设计更加聚焦到软件工艺这一个层次,是一个非常技术性的话题,我们以后的讨论都会集中在这个层次的讨论,所以在以后的讨论中我们不再考虑具体的业务需求之类的问题。 刚刚提交就有一位朋友不满了,软件工艺中的需求和业务需求考虑的着眼点不同,但是评判的标准都是一致的就是:平衡。但是解决的问题是不一样的,一个解决的是业务问题,一个解决的是技术问题。在极端情况下,需求文档一个字都不改,整个项目重做的情况也不是不可能发生。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tof21

支持原创

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值