“抽象”是语言提供的功能。“多态”由l承语义实现?
如此Q问题生了Q“我们如何去度量l承关系的质量??
Liskov?987q提Z一个关于承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“承必ȝ保超cL拥有的性质在子cM仍然成立。”也是_当一个子cȝ实例应该能够替换M其超cȝ实例Ӟ它们之间才具有is-A关系?
该原则称为Liskov Substitution Principle——里氏替换原则。林先生在上课时风趣地称之ؓ“老鼠的儿子会打洞”。^_^
我们来研I一下LSP的实质。学习OO的时候,我们知道Q一个对象是一l状态和一pd行ؓ的组合体。状态是对象的内在特性,行ؓ是对象的外在Ҏ。LSP所表述的就是在同一个承体pM的对象应该有共同的行为特征?
q一点上Q表明了OO的承与日常生活中的l承的本质区别。D一个例子:生物学的分类体系中把企鹅归属为鸟cR我们模仿这个体p,设计Lcd关系?
cZ鸟”中有个ҎflyQ企鹅自然也l承了这个方法,可是企鹅不能飞阿Q于是,我们在企鹅的cM覆盖了flyҎQ告诉方法的调用者:企鹅是不会飞的。这完全W合常理。但是,q违反了LSPQ企鹅是鸟的子类Q可是企鹅却不能飞!需要注意的是,此处的“鸟”已l不再是生物学中的鸟了,它是软g中的一个类、一个抽象?
有h会说Q企鹅不能飞很正常啊Q而且q样~写代码也能正常~译Q只要在使用q个cȝ客户代码中加一句判断就行了。但是,q就是问题所在!首先Q客户代码和“企鹅”的代码很有可能不是同时设计的,在当今Y件外包一层又一层的开发模式下Q你甚至Ҏ不知道两个模块的原地是哪里Q也p不上M改客户代码了。客L序很可能是遗留系l的一部分Q很可能已经不再l护Q如果因计出q么一个“企鹅”而导致必M改客户代码,谁应该承担这部分责Q呢?Q大概是上帝吧,谁叫他让“企鹅”不能飞的。^_^Q“修改客户代码”直接违反了OCPQ这是OCP的重要性。违反LSP既有的设计不能封闭!
修正后的设计如下Q?
但是Q这是LSP的全部了么?书中l了一个经典的例子Q这又是一个不W合常理的例子:正方形不是一个长方Ş。这个悖论的详细内容能在|上扑ֈQ我׃多废话了?
LSPq没有提供解册个问题的ҎQ而只是提Zq么一个问题?
于是Q工E师们开始关注如何确保对象的行ؓ?988q_B. Meyer提出了Design by ContractQ契U式设计Q理论。DbC从Ş式化Ҏ中借鉴了一套确保对象行为和自n状态的ҎQ其基本概念很简单:
以上是单个对象的U束条g。ؓ了满LSPQ当存在l承关系Ӟ子类中方法的前置条g必须与超cM被覆盖的Ҏ的前|条件相同或者更宽松Q而子cMҎ的后|条件必M类中被覆盖的方法的后置条g相同或者更Z根{?
一些OO语言中的Ҏ能够说明这一问题Q?
可以看出Q以上这些特性都非常好地遵@了LSP。但是DbC呢?很遗憾,L的面向对象语aQ不论是动态语aq是静态语aQ还没有加入对DbC的支持。但是随着AOP概念的生,怿不久DbC也将成ؓOO语言的一个重要特性之一?
一些题外话Q?
OCP的动机很单:软g是变化的。不论是优质的设计还是低劣的设计都无法回避这一问题。OCP说明了Y件设计应该尽可能C架构E_而又Ҏ满不同的需求?
Z么要OCPQ答案也很简单——重用?
“重用”,q不是什么Y件工E的专业词汇Q它是工E界所q的词汇。早在Y件出现前Q工E师们就在实践“重用”了。比如机C品,通过雉件的l装得到最l的能够使用的工兗由于机械部件的设计和制造过E是极其复杂的,所以互换性是一个重要的Ҏ。一辆R可以用不同的发动机、不同的变速箱、不同的轮胎……很多东西我们直接买来装上就可以了。这也是一个OCP的例子。(可能是由于我是搞机械n的吧Q所以就举些机械斚w的例子^_^Q?
如何在OO中引入OCP原则Q把对实体的依赖改ؓҎ象的依赖p了。下面的例子说明了这个过E:
05赛季的时候,一辆F1赛R有一台V10引擎。但是到?6赛季Q国际汽联修改了规则Q一辆F1赛R只能安装一台V8引擎。R队很快投入了新赛车的研发Q不q的是,从工E师那里得到消息Q旧车n的设计不能够装进新研发的引擎。我们不得不为新的引擎重新打造Rw,于是一辆新的赛车诞生了。但是,ȝ的事接踵而来Q国际汽联频频修改规则,搞得设计师在“赛车”上改了又改Q最l变得不成样子,只能把它废弃?
Z能够重用q辆昂贵的赛车,工程师们提出了解x案:首先Q在车n的设计上预留出安装引擎的位置和管Uѝ然后,Ҏq些设计好的规范设计引擎Q或是引擎的适配器)。于是,新的赛R设计Ҏp栯生了?
昄Q通过重构Q这里应用的是一个典型的Bridge模式。这个实现的关键之处在于我们预先l引擎留Z位置Q我们不必因为对引擎的规则的频频变更而制造相当多的Rw,而是可能地沿用和改良现有的车n?BR>说到q里Q想说一说OO设计的一个误区?BR>学习OO语言的时候,Z能够说明“扎쀝(或者说“is-a”)q个概念Q教U书上经常用实际生活中的例子来解释。比如汽车是车,电R是RQF1赛R是汽车,所以R是汽车、电车、F1赛R的上层抽象。这个例子ƈ没有错。问题是Q这L例子q于“Ş象”了Q如果OO设计直接可以将现实生活中的概念引用q来Q那也就不需要什么Y件工E师了!OO设计的关键概忉|抽象。如果没有抽象,那所有的软g工程师的努力都是徒劳的。因为如果没有抽象,我们只能L造世界中每一个对象。上面这个例子中Q我们应该看到“引擎”这个抽象的存在Q因R队的工程师们为它预留了位|,为它制定了设计规范?BR>上面q个设计也实C后面要说的DIPQ依赖倒置原则Q。但是请CQOCP是OO设计原则中高层次的原则,其余的原则对OCP提供了不同程度的支持。ؓ了实现OCPQ我们会自觉或者不自觉地用到其它原则或是诸如Bridge、Decorator{设计模式。然而,对于一个应用系l而言Q实现OCPq不是设计目的,我们所希望的只是一个稳定的架构。所以对OCP的追求也应该适可而止Q不要陷入过渡设计。正如Martin本h所_“No significant program can be 100% closed.”“Closure not complete but strategic?BR>
Q下一就要讲LSP了,我觉得这是意义最为重要的OO设计原则Q它直指当今LOO语言的Y肋,点出了OO设计的精髓。)
在学习和使用OO设计的时候,我们应该明白QOO的出C得Y件工E师们能够用更接q真实世界的Ҏ描述软gpȝ。然而,软g毕竟是徏立在抽象层次上的东西Q再怎么接近真实Q也不能替代真实或被真实替代?/P>
OO设计的五大原则之间ƈ不是怺孤立的。彼此间存在着一定关联,一个可以是另一个原则的加强或是基础。违反其中的某一个,可能同时q反了其余的原则。因此应该把q些原则融会贯通,牢记在心Q?/P>
1. SRPQSingle Responsibility Principle 单一职责原则Q?BR> 单一职责很容易理解,也很Ҏ实现。所谓单一职责Q就是一个设计元素只做一件事。什么是“只做一件事”?单说是管闲事。现实中是如此Q如果要你专心做一件事情,M人都有信心可以做得很。但如果Q你整天被ؕ七八p的事所累,q有心思和_֊把每件事都作好么Q?BR>
“单一职责”就是要在设计中为每U职责设计一个类Q彼此保持正交,互不q涉。这个雕塑(二重奏)是正交的一个例子,钢琴家和提琴家各自演奏自己的乐谱,而结果就是一个和谐的交响乐。当Ӟ真实世界中,演奏提琴和弚w琴的必须是两个hQ但是在软g中,我们往往会把两者甚x多搅和到一P很多时候只是ؓ了方便或是最初设计的时候没有想到?nbsp;
q样的例子在设计中很常见Q书中就l了一个很好的例子Q调制解调器。这是一个调制解调器最基本的功能。但是这个类事实上完成了两个职责Q连接的建立和中断、数据的发送和接收。显Ӟq违反了SRP。这样做会有潜在的问题:当仅需要改变数据连接方式时Q必M改Modemc,而修改Modemcȝl果是使得M依赖Modemcȝ元素都需要重新编译,不管它是不是用到了数据连接功能。解决的办法Q书中也已经l出Q重构Modemc,从中抽出两个接口Q一个专门负责连接、另一个专门负责数据发送。依赖Modemcȝ元素也要做相应的l化Q根据职责的不同分别依赖不同的接口。最后由ModemImplementationcd现这两个接口?BR>
从这个例子中Q我们不隑֏玎ͼq反SRP通常是由于过于“真实”地设计了一个类所造成的。因此,解决办法是往更高一层进行抽象化提取Q将Ҏ个具体类的依赖改变ؓ对一l接口或抽象cȝ依赖。当Ӟq个抽象化的提取应该Ҏ需要设计,而不是盲目提取。比如刚才这个Modem的例子中Q如果有必要Q还可以把DataChannel抽象为DataSender和DataReceiver两个接口?BR>
从项目管理的角度ȝ解,设计是ؓ了满x众(StakeholdersQ的需求。显Ӟ一个设计应该满_户对pȝ的功能及非功能需求。但单是满了这一点,q不能称Z个好的设计。因为开发者同样属于涉众!而开发者的需求又是怎样的呢Q至,应该有以下几条吧Q?
所以,设计应该可能多地照ֈl护和变更?
Z兼顾各户满意和维护成本,设计应该不断挑战其终极目标——松耦合。不是XP或UPQ这个目标都不会改变。OO设计中的五大原则Q其Ҏ目的是降低lg间的耦合度,避免牵一发则动全w的现象发生。降低耦合度不仅能够提高Y件内在的质量Q还能大大减不必要的编译时间、减向版本控制pȝ提交源码的网l开销…?BR>
如何鉴别设计的这一指标QY件工E中有专用的度量QCBOQCoupling Between Objects)Q那是由公式计算出来的,也有很多工具支持Q值得一试。(听过几次李维先生的讲座,他经常拿Together的度量功能炫耀^_^Q?
但是Q作Z个开发h员,Ҏ中的代码应该有适当的敏感性。毕竟,q些代码是你亲手创造的Q谁不希望自q作品得到众h的赞许?或许能换得一ơ加薪升职的Z^_^ 退一步,q可关系到宝늚休息旉啊。所以,开发者应该对自己的品有q样一U意识:及时修正设计中不合理的地斏V?
敏捷q程告诉我们Q在代码“有味道”的时候进行重构。“有味道”是代码正在变质的标志,很遗憾,能够使代码保持原味的防腐剂还没发明。ؓ了保证代码质量,及时重构是必要的。这像在烧烤的时候ؓ了防止烤焦,你得坐在炉子前经常翻动肉块一栗?
如何d代码的味道?认真学习一下OO吧,别以为OO很简单,是l承+装+多态,谁都会。即使是书中记述的五大原则,惌q用自如Q也得多感觉感觉才行。很多时候,我们不知不觉把蛆虫放进了代码中…?
好了Q下一:OO五大原则
最q正在读q本书,喜欢影印版,是因Z中漂亮的插图。:Q惭愧,如此的好书到现在才去诅R?BR>准备边读边记录些心得Q今天先说些废话。:P
先粗略地概览了一遍全书。本书主要分以下几个部分Q?
之所以觉得这本书好,q与一个h有关。就是交大Y件学院的林d彰老师。林先生的课Q风幽默,能够用直观Ş象的语言让学生对讲课内容产生深刻的印象。(我可不是托儿Q网上能搜到些林先生讲课的片断,要是怀疑,可以验证一番)。记得在软g工程q门NQ林先生l我们讲了很多有兌计原则的内容Q其中就有“开闭原则(OCPQ”、“里氏替换原则(LSPQ”等……就把这本书当作是一本补充读物吧?
a归正传。个人感觉这本书的M风格Q就和所要讲的“敏捷”一Pq不带着厚重的学院派风味Q而是更注重实cƈ不是没有理论Q只是把理论融入C实践中,化了理论的复杂性。读h感觉很带劲儿?
废话说到q里Q下一步的计划是跟着自己的进度写M心得了。我x对书中内容的理解和以前在林先生的课上所学的l合在一P导出阅读此书时的大脑zd镜像?/P>