面向对象葵花宝典——设计.docx_第1页
面向对象葵花宝典——设计.docx_第2页
面向对象葵花宝典——设计.docx_第3页
面向对象葵花宝典——设计.docx_第4页
面向对象葵花宝典——设计.docx_第5页
已阅读5页,还剩54页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1. 设计原则内聚&耦合 前面通过实例讲解了一个一环扣一环的面向对象的开发流程:用例模型 - 领域模型 - 设计模型(类模型 + 动态模型),解答了面向对象如何做的问题。接下来我们就要讲“如何做好面向对象设计”的技巧了【内聚】参考维基百科的解释,内聚的含义如下:cohesionreferstothedegreetowhichtheelementsofamodulebelongtogether.翻译一下即:内聚指一个模块内部元素彼此结合的紧密程度。看起来很好理解,但深入思考一下,其实没有那么简单。首先:“模块”如何理解?你一定会说,“模块”当然就是我们所说的系统里的“XX模块”了,例如一个ERP系统的“权限”模块,一个电子商务的“支付”模块,一个论坛网站的“用户管理”模块。等等你说的没错,但在面向对象领域,谈到“内聚”的时候,模块的概念远远不止我们通常所理解的“系统内的某个模块”这个范围,而是可大可小,大到一个子系统,小到一个函数,你都可以理解为内聚里所说的“模块”。所以,你可以用“内聚”来判断一个函数设计是否合理,一个类设计是否合理,一个接口设计是否合理,一个包的设计是否合理,一个模块/子系统设计是否合理。其次:“元素”究竟是什么?有了前面对“模块”的深入研究后,元素的含义就比较容易明确了(不同语言稍有不同)。函数:函数的元素就是“代码”类/接口:类的元素是“函数、属性”包:包的元素是“类、接口、全局数据”等模块:模块的元素是“包、命名空间”等再次:“结合”是什么?英文的原文是“belong”,有“属于”的意思,翻译成中文“结合”,更加贴近中文的理解。但“结合”本身这个词容易引起误解。绝大部分人看到“结合”这个单词,想到的肯定是“你中有我、我中有你”这样的含义,甚至可能会联想到“美女和帅哥”的结合,抑或“青蛙王子和公主”的结合这种情况。这样的理解本身也并没有错,但比较狭隘。我们以类的设计为例:假如一个类里面的函数都是只依赖本类其它函数(当然不能循环调用啦),那内聚性肯定是最好的,因为“结合”得很紧密。但如果这个类的函数并不依赖本类的函数呢?我们就一定能说这个类的内聚性不好么?其实也不尽然,最常见的就是CRUD操作类,这几个函数相互之间没有任何结合关系(某些设计可能会先查询再修改,但这样的设计不是事务安全的),但其实这几个函数的内聚性非常高。所以,关于内聚的结合概念,我认为不是非常恰当的描述。那么,就究竟什么才是真正的“内聚”呢?答案就藏在显而易见的地方,翻开你的词典,仔细看看cohesion的含义,你会看到另外一个解释:凝聚力!“凝聚力”就是“内聚”的核心思想,抛开面向对象不谈,我们日常工作中几乎随处可见“凝聚力”:你可能会说,你的团队很有凝聚力。领导可能会说:我们要增强团队的凝聚力。成功学大师会说:凝聚力是一个团队成功的基石。面向对象领域的“凝聚力”,和团队的“凝聚力”是一样的概念。判断团队凝聚力时,我们关注团队成员是否都专注于团队的目标;判断面向对象模块的凝聚力时,我们同样关注元素是否专注于模块的目标,即:模块本身的职责!判断团队凝聚力时,我们还会关注团队成员之间是否互相吸引和帮助;判断面向对象模块凝聚力时,我们同样关注元素间的结合关系;虽然判断内聚性的时候我们会考虑元素的结合情况,但其实是否专注模块的职责,才是内聚性的充要条件。当模块的元素全部都专注于模块的职责的时候,即使元素间的结合不是很紧密,也是符合内聚性的要求的,这也是CRUD设计符合内聚性的原因。所以,判断一个模块(函数、类、包、子系统)“内聚性”的高低,最重要的是关注模块的元素是否都忠于模块的职责,简单来说就是“不要挂羊头卖狗肉”。【耦合】参考维基百科,耦合的定义如下:couplingordependencyisthedegreetowhicheachprogrammodulereliesoneachoneoftheothermodules。简单翻译一下:耦合(或者称依赖)是程序模块相互之间的依赖程度。从定义来看,耦合和内聚是相反的:内聚关注模块内部的元素结合程度,耦合关注模块之间的依赖程度。理解耦合的关键有两点:什么是模块,什么是依赖。什么是模块?模块和内聚里面提到的模块一样,耦合中的模块其实也是可大可小。常见的模块有:函数、类、包、子模块、子系统等什么是依赖?依赖这个词很好理解,通俗的讲就是某个模块用到了另外一个模块的一些元素。例如:A类使用了B类作为参数,A类的函数中使用了B类来完成某些功能。等等。2. 高内聚低耦合 高内聚低耦合,可以说是每个程序猿,甚至是编过程序,或者仅仅只是在大学里面学过计算机,都知道的一个简单的设计原则。虽然如此流行和人所众知,但其实真正理解的人并不多,很多时候都是人云亦云。要想真正理解“高内聚低耦合”,需要回答两个问题:1)为什么要高内聚低耦合?2)高内聚低耦合是否意味内聚越高越好,耦合越低越好?第一个问题:为什么要高内聚低耦合?经典的回答是:降低复杂性。确实很经典,当然,其实也是废话!我相信大部分人看了后还是不懂,什么叫复杂性呢?要回答这个问题,其实可以采用逆向思维,即:如果我们不做到这点,将会怎样?首先来看内聚,试想一下,假如我们是低内聚,情况将会如何?前面我们在阐述内聚的时候提到内聚的关键在于“元素的凝聚力”,如果内聚性低,则说明凝聚力低;对于一个团队来说,如果凝聚力低,则一个明显的问题是“不稳 定”;对于一个模块来说,内聚性低的问题也是一样的“不稳定”。具体来说就是如果一个模块内聚性较低,则这个模块很容易变化。一旦变化,设计、编码、测 试、编译、部署的工作量就上来了,而一旦一个模块变化,与之相关的模块都需要跟着改变。举一个简单的例子,假设有这样一个设计不好的类:Person,其同时具有“学生”、“运动员”、“演员”3个职责,有另外3个类“老师”、“教练”、“导演”依赖这个类。Person.java1. packagecom.oo.cohesion.low;2. 3. /*4. *“人”的类设计5. *6. */7. publicclassPerson8. 9. /*10. *学生的职责:学习11. */12. publicvoidstudy()13. /TODO:studentsresponsibility14. 15. 16. /*17. *运动员的职责:运动18. */19. publicvoidplay()20. /TODO:sportsmansresponsibility21. 22. 23. /*24. *演员的职责:扮演25. */26. publicvoidact()27. /TODO:actorsresponsibity28. 29. Teacher.java1. packagecom.oo.cohesion.low;2. 3. /*4. *“老师”的类设计5. *6. */7. publicclassTeacher8. 9. publicvoidteach(Personstudent)10. student.study();/依赖Person类的“学生”相关的职责11. 12. Coach.java1. packagecom.oo.cohesion.low;2. 3. /*4. *“教练”的类设计5. *6. */7. publicclassCoach8. 9. publicvoidtrain(Persontrainee)10. trainee.play();/依赖Person类的“运动员”职责11. 12. Director.java1. packagecom.oo.cohesion.low;2. 3. /*4. *“导演”的类设计5. *6. */7. 8. publicclassDirector9. 10. publicvoiddirect(Personactor)11. actor.act();/依赖Person类“演员”的相关职责12. 13. 在上面的样例中,Person类就是一个典型的“低内聚”的类,很容易发生改变。比如说,现在老师要求学生也要考试,则Person类需要新增一个方法:test,如下:1. packagecom.oo.cohesion.low;2. 3. /*4. *“人”的类设计5. *6. */7. publicclassPerson8. 9. /*10. *学生的职责:学习11. */12. publicvoidstudy()13. /TODO:studentsresponsibility14. 15. 16. /*17. *学生的职责:考试18. */19. publicvoidtest()20. /TODO:studentsresponsibility21. 22. 23. /*24. *运动员的职责:运动25. */26. publicvoidplay()27. /TODO:sportsmansresponsibility28. 29. 30. /*31. *演员的职责:扮演32. */33. publicvoidact()34. /TODO:actorsresponsibity35. 36. 由于Coach和Director类都依赖于Person类,Person类改变后,虽然这个改动和Coach、Director都没有关系,但Coach和Director类都需要重新编译测试部署(即使是PHP这样的脚本语言,至少也要测试)。同样,Coach和Director也都可能增加其它对Person的要求,这样Person类就需要同时兼顾3个类的业务要求,且任何一个变化,Teacher、Coach、Director都需要重新编译测试部署。对于耦合,我们采用同样的方式进行分析,即:如果高耦合,将会怎样?高耦合的情况下,模块依赖了大量的其它模块,这样任何一个其它依赖的模块变化,模块本身都需要受到影响。所以,高耦合的问题其实也是“不稳定”,当然,这个 不稳定和低内聚不完全一样。对于高耦合的模块,可能本身并不需要修改,但每次其它模块修改,当前模块都要编译、测试、部署,工作量同样不小。我们同样以一个样例来说明。假设我们要设计一个Boss类,Boss是要管整个公司的,那么我们假设这是一家“麻雀虽小五脏俱全”的公司,同时有“研发、测试、技术支持、销售、会计、行政”等部门。Boss.java1. packagecom.oo.coupling.high;2. 3. /*4. *“老板”类5. *6. */7. publicclassBoss8. 9. privateTestertester;10. privateDeveloperdeveloper;11. privateSupportersupporter;12. privateAdministrationadmin;13. privateAccountantaccountant;14. 15. /*16. *Boss每天检查工作,看到下面的代码,是否觉得Boss很忙?17. */18. publicvoidcheck()19. 20. /检查测试工作21. tester.report();22. 23. /检查研发工作24. developer.report();25. 26. /检查技术支持工作27. supporter.report();28. 29. /检查行政工作30. admin.report();31. 32. /检查财务工作33. accountant.report();34. 35. Accountant.java1. packagecom.oo.coupling.high;2. 3. /*4. *“财务”类5. *6. */7. publicclassAccountant8. 9. publicvoidreport()10. System.out.print(Accountantreport);11. 12. Administration.java1. packagecom.oo.coupling.high;2. 3. /*4. *“行政”类5. *6. */7. publicclassAdministration8. 9. publicvoidreport()10. System.out.print(Administrationreport);11. 12. Developer.java1. packagecom.oo.coupling.high;2. 3. /*4. *“开发”类5. *authorAdministrator6. *7. */8. publicclassDeveloper9. 10. publicvoidreport()11. System.out.print(Developerreport);12. 13. Supporter.java1. packagecom.oo.coupling.high;2. 3. /*4. *“技术支持”类5. *6. */7. publicclassSupporter8. 9. publicvoidreport()10. System.out.print(Supporterreport);11. 12. Tester.java1. packagecom.oo.coupling.high;2. 3. /*4. *“测试”类5. *6. */7. publicclassTester8. 9. publicvoidreport()10. System.out.print(Testerreport);11. 12. 好吧,Boss很忙,我们也很钦佩,但是有一天,研发的同学觉得他们应该做更多的事情,于是他们增加了一个“研究”的工作,且这个工作也不需要报告给Boss,例如:java view plaincopy1. packagecom.oo.coupling.high;2. 3. /*4. *“开发”类5. *authorAdministrator6. *7. */8. publicclassDeveloper9. 10. publicvoidreport()11. System.out.print(Developerreport);12. 13. 14. /*15. *研发新增加一个研究的任务,这个任务也不需要向Boss汇报16. */17. publicvoidresearch()18. System.out.print(Developerisresearchingbigdata,hadoop:);19. 20. 虽然这个工作不需要报告给Boss,但由于Developer类修改了,那么Boss类就需要重新编译测试部署。其它几个Tester、Supporter 类等也是类似,一旦改变,即使这些类和Boss类半毛钱关系都没有,Boss类还是需要重新编译测试部署,所以Boss类是很不稳定的。所以,无论是“低内聚”,还是“高耦合”,其本质都是“不稳定”,不稳定就会带来工作量,带来风险,这当然不是我们希望看到的,所以我们应该做到“高内聚低耦合”。回答完第一个问题后,我们来看第二个问题:高内聚低耦合是否意味着内聚越高越好,耦合越低越好?按照我们前面的解释,内聚越高,一个类越稳定;耦合越低,一个类也很稳定,所以当然是内聚越高越好,耦合越低越好了。但其实稍有经验的同学都会知道这个结论是错误的,并不是内聚越高越好,耦合越低越好,真正好的设计是在高内聚和低耦合间进行平衡,也就是说高内聚和低耦合是冲突的。我们详细来分析一下为什么高内聚和低耦合是冲突的。对于内聚来说,最强的内聚莫过于一个类只写一个函数,这样内聚性绝对是最高的。但这会带来一个明显的问题:类的数量急剧增多,这样就导致了其它类的耦合特别多,于是整个设计就变成了“高内聚高耦合”了。由于高耦合,整个系统变动同样非常频繁。同理,对于耦合来说,最弱的耦合是一个类将所有的函数都包含了,这样类完全不依赖其它类,耦合性是最低的。但这样会带来一个明显的问题:内聚性很低,于是整个设计就变成了“低耦合低内聚”了。由于低内聚,整个类的变动同样非常频繁。对于“低耦合低内聚”来说,还有另外一个明显的问题:几乎无法被其它类重用。原因很简单,类本身太庞大了,要么实现很复杂,要么数据很大,其它类无法明确该如何重用这个类。所以,内聚和耦合的两个属性,排列组合一下,只有“高内聚低耦合”才是最优的设计。因此,在实践中我们需要牢牢记住需要在高内聚和低耦合间进行平衡,而不能走极端。 具体如何平衡,且听下回分解。3. SRP原则 前面详细阐述了“高内聚低耦合”的总体设计原则,但如何让设计满足这个原则,并不是一件简单的事情,幸好各位前辈和大牛已经帮我们归纳总结出来了,这就是“设计原则”和“设计模式”。毫不夸张的说,只要你吃透这些原则和模式并熟练应用,就能够做出很好的设计。3.1. 【SRP原则详解】SRP,singleresponsibilityprinciple,中文翻译为“单一职责原则”!这是面向对象类设计的第一个原则,也是看起来最简单的一个原则,但这实际上远远没有那么简单,很多人都不一定真正理解了!我们随便找几个网上的解释,看看各位大师或者经典网站是如何解释的:百度百科(/view/1545205.htm):一个类应该有且仅有一个职责。关于职责的含意,面向对象大师Robert.C.Martin有一个著名的定义:所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,其实就是耦合了多个互不相关的职责,就会降低这个类的内聚性。说句实话,虽然是面向对象大师Martin的解释,我还是看得不甚明白:引起类变化的原因太多了,例如:给类加一个方法是变化吧?给类加一个属性是变化吧?类的函数增加一个参数是变化吧?。引起这些变化的原因太多了,如果每个原因都是一个职责,那SRP原则简直就没法判断了!Wiki百科(/wiki/Single_responsibility_principle)内容和百度百科基本一致,看起来百度百科像wiki百科的翻译:)Martindefinesaresponsibilityasareasontochange,andconcludesthataclassormoduleshouldhaveone,andonlyone,reasontochange.除了这些标准的解释外,还有一种说法:SRP就是指每个类只做一件事!这个解释更通俗易懂,也更加适合中国人理解。虽然比Martin大师的解释更清楚一些,但仔细想想,还是有个地方比较难以理解:什么叫做“一件事”?比如说一个学生信息管理类,这个类有“添加学生信息”、“查询学生信息”、“修改学生信息”、“删除学生信息”,那么这是4件事情,还是一件事情呢?看起来好像是4个事情,但稍有经验的朋友应该都知道,这4个事情绝大部分情况下都是一个类来实现的,而不是分成4个类!所以关键的问题在于:什么是“一件事”?是每个功能一件事情么?其实答案就在我们自己身上,因为只要我们工作,无时不刻的承担着一定的“职责”!现在让我们抛开面向对象,抛开软件,抛开计算机,来看看我们自己的“职责”。比如说,我是一个程序猿,我的职责应该是“写程序”,但写程序有很多事情,例如:编码,单元测试、系统测试,bug修复,开会,写文档。再比如说,我的BOSS是一个管理者,他的职责是“管理程序猿”,他也有很多工作,例如:制定计划,团队建设、开会、协调资源、写文档。又比如说,我是一个快递员,也有很多工作:分包、快递、收款、开会。这些职责其实都不是我们自己定义的,而是公司或者部门或者组织给我们安排工作的时候定义的,也就是说:“职责”是站在他人的角度来定义的,而不是自己定义的,也许你想把自己定义成CEO,但你的老板会真的让你当CEO么?经过对我们自己的职责的分析,我们可以得出两个关于职责的重要结论:1)职责是站在他人的角度来定义的2)职责不是一件事,而是很多事情,但这些事情都是和职责紧密相关的对应到面向对象设计领域,我们可以说一个类的职责应该如下定义:1)类的职责是站在其它类的角度来定义的;2)类的职责包含多个相关功能;因此,SRP可以翻译成“一个类只负责一组相关的事情”,对应到代码中就是:一个类有多个方法,这些方法是相关的。当然,如果你再让我解释什么是“相关”,那我只能吐血了:)有了这个定义,我们再来看“学生信息管理类”,很明显,学生管理类具有的4个功能都是和“管理”相关的,按照SRP原则,应该只设计一个“学生信息管理类”就可以了。3.2. 【SRP原则范围】但现实世界往往比理想要复杂,一个最典型的例子就是“办公一体机”。根据SRP原则,打印机可以设计成一个类,复印机也可以设计成一个类,扫描仪也可以设计成一个类,传真机还是可以设计成一个类,但偏偏就是出了个“办公一体机”,这个机器集成了“打印、复印、扫描、传真”4个职责!如果我们要设计一个“办公一体机”的类,怎么也不可能设计出一个符合SRP原则的“办公一体机”的类来!怎么办?是SRP不正确么,还是我们永远都不要设计“办公一体机”这样的类?其实SRP也没有错,“办公一体机”也应该设计,但:不要用SRP来约束“办公一体机”这样的类!也就是说,SRP其实是有适应范围的,SRP只适合那些基础类,而不适合基于基础类构建复杂的聚合类。在“办公一体机“的样例中,“打印机”、“复印机”、“扫描仪”、“传真机”都是基础类,每个类都承担一个职责,而办公一体机是“聚合类”,同时集成了4种功能!细心的朋友可能会继续问道:SRP不能应用于聚合类,那么如何保证聚合类的设计质量呢?这个问题的答案在GoF的设计模式一书中有详细的答案,即:优先使用对象组合,而不是类继承。详细内容请参考后续“设计模式”部分的内容4. OCP原则 开闭原则是一个大部分人都知道,但大部分人都不懂的设计原则!OCP,Open-ClosedPrinciple,中文翻译为“开闭原则”。当我第一次看到OCP原则时,我的感觉就是这原则也太抽象了吧,什么开,什么闭呢?然后我去寻找更加详细的答案,最经典也是最常见的解释就是维基百科了:softwareentities(classes,modules,functions,etc.)shouldbeopenforextension,butclosedformodification;翻译一下就是:对扩展开放,对修改封闭!虽然这句解释更详细了,但其实还是很难理解,我因此去请教了一个前辈高人,他的回答更加惊世骇俗:不修改代码就可以增加新功能!当时我听到这句话就震惊了,这是多么神奇的事情啊,不修改代码就能够增加新功能!但问题是:怎么做到的呢?难道这个原则是有关人工智能,又或者有什么高超的技巧,能够做到不修改代码增加新功能?这么牛逼的原则当然要继续探索了,但怎么也没有找到“不修改代码就可以增加新功能”的独门秘籍!于是对这个原则有了怀疑,经过继续的探索和查看各种资料,才发现原来是各位大师们在解释这个原则的时候隐藏了非常重要的“主语”,而这才是OCP原则的关键!大师们省略的主语一个就是consumer(翻译成使用者、消费者),一个就是provider(翻译成生产者、提供着),例如A类调用了B类的方法,则A就是consumer,B就是provider。完整的OCP原则实际上应该这样表述:openforproviderextension,closedforconsumermodification翻译一下就是:对使用者修改关闭,对提供者扩展开放!更通俗的意思就是:提供者增加新的功能,但使用者不需要修改代码!虽然到这里我们已经基本上将OCP原则解释清楚了,但实际上细心的朋友还是会发现有问题的:提供者增加新的功能,使用者不修改代码就能用上么?比如说:你设计一款有关车游戏,需要设计一个“car”的类,这个类原来有“加速”、“刹车”、“转向”三个功能,现在你要加一个新功能“改装”,游戏中其它类例如player,不修改代码就可以用上“改装”这个功能么?很显然这是不可能的,我都新加了一个函数,你都不调用就能用新的功能,这也太邪乎了吧?答案在于所谓的增加新功能,并不是增加一个全新的功能,而是原有的功能有了替代实现,这也是英文的“extension”所隐含的深意!继续以赛车car作为例子,假设现在你设计了“卡车”、“跑车”、“家用车”三种车,现在要增加一种车“卡丁车”,只要“卡丁车”也实现了“加速”、“刹 车”、“转向”,那么player不需要修改代码,就可以玩“卡丁车”了;但如果你增加了一种“改装”的功能,那么player必须修改才能使用“改装” 功能。对应到代码上来说,OCP的应用原则如下:1)接口不变:包括函数名、函数参数、函数返回值等,可以应用OCP2)接口改变:已有函数修改名称、参数、返回值,或者增加新的函数,OCP都不再适应虽然OCP原则是针对类设计提出来的原则,但其思想其实适应很广,系统和系统、子系统和子系统、模块和模块之间都可以应用OCP原则,而且不同的地方应用其实都是遵循同一个原则:通过接口交互!例如:1)类之间应用OCP:使用interface进行交互;2)模块和模块、系统和系统:使用规定好的协议,不管是私有的还是公开的,例如HTTP、SOAP。5. LSP原则 LSP是唯一一个以人名命名的设计原则,而且作者还是一个“女博士”LSP,Liskovsubstitutionprinciple,中文翻译为“里氏替换原则”。这是面向对象原则中唯一一个以人名命名的原则,虽然Liskov在中国的知名度没有UNIX的几位巨匠(KennethThompson、 DennisRitchie)、GOF四人帮那么响亮,但查一下资料,你会发现其实Liskov也是非常牛的:2008年图灵奖获得者,历史上第一个女 性计算机博士学位获得者。其详细资料可以在维基百科上查阅:/wiki/Barbara_Liskov言归正传,我们来看看LSP原则到底是怎么一回事。LSP最原始的解释当然来源于Liskov女士了,她在1987年的OOPSLA大会上提出了LSP原则,1988年,她将文章发表在ACM的SIGPLANNotices杂志上,其中详细解释了LSP原则:Atypehierarchyiscomposedofsubtypesandsupertypes.Theintuitiveideaofasubtypeisonewhoseobjectsprovideallthebehaviorofobjectsofanothertype(thesupertype)plussomethingextra.Whatiswantedhereissomethinglikethefollowingsubstitutionproperty:Ifforeachobjecto1oftypeSthereisanobjecto2oftypeTsuchthatforallprogramsPdefinedintermsofT,thebehaviorofPisunchangedwheno1issubstitutedforo2thenSisasubtypeofT.英文比较长,看起来比较累,我们简单的翻译并归纳一下:1)子类的对象提供了父类的所有行为,且加上子类额外的一些东西(可以是功能,也可以是属性);2)当程序基于父类实现时,如果将子类替换父类而程序不需要修改,则说明符合LSP原则虽然我们稍微翻译和整理了一下,但实际上还是很拗口和难以理解。幸好还有Martin大师也觉得这个不怎么通俗易懂,RobertMartin在1996年为C+Reporter写了一篇题为TheTheLiskovSubstitutionPrinciple的文章,解释如下:Functionsthatusepointersorreferencestobaseclassesmustbeabletouseobjectsofderivedclasseswithoutknowingit.翻译一下就是:函数使用指向父类的指针或者引用时,必须能够在不知道子类类型的情况下使用子类的对象。Martin 大师解释了一下,相对容易理解多了。但Martin大师还不满足,在2002年,Martin在他出版的 AgileSoftwareDevelopmentPrinciplesPatternsandPractices 一书中,又进一步简化为:Subtypesmustbesubstitutablefortheirbasetypes。翻译一下就是:子类必须能替换成它们的父类。经过Martin大师的两次翻译,我相信LSP原则本身已经解释得比较容易理解了,但问题的关键是:如何满足LSP原则?或者更通俗的讲:什么情况下子类才能替换父类?我们知道,对于调用者来说(Liskov解释中提到的P),和父类交互无非就是两部分:调用父类的方法、得到父类方法的输出,中间的处理过程,P是无法知道的。也就是说,调用者和父类之间的联系体现在两方面:函数输入,函数输出。详细如下图:有了这个图之后,如何做到LSP原则就清晰了:1)子类必须实现或者继承父类所有的公有函数,否则调用者调用了一个父类中有的函数,而子类中没有,运行时就会出错;2)子类每个函数的输入参数必须和父类一样,否则调用父类的代码不能调用子类;3)子类每个函数的输出(返回值、修改全局变量、插入数据库、发送网络数据等)必须不比父类少,否则基于父类的输出做的处理就没法完成。有了这三条原则后,就可以很方便的判断类设计是否符合LSP原则了。需要注意的是第3条的关键是“不比父类少”,也就是说可以比父类多,即:父类的输出是子类输出的子集。有的朋友看到这三条原则可能有点纳闷:这三条原则一出,那子类还有什么区别哦,岂不都是一样的实现了,那还会有不同的子类么?其实如果仔细研究这三条原则,就会发现其中只是约定了输入输出,而并没有约束中间的处理过程。例如:同样一个写数据库的输出,A类可以是读取XML数据然后写入数据库,B类可以是从其它数据库读取数据然后本地的数据库,C类可以是通过分析业务日志得到数据然后写入数据库。这3个类的处理过程都不一样,但最后都写入数据到数据库了。LSP 原则最经典的例子就是“长方形和正方形”这个例子。从数学的角度来看,正方形是一种特殊的长方形,但从面向对象的角度来观察,正方形并不能作为长方形的一 个子类。原因在于对于长方形来说,设定了宽高后,面积=宽*高;但对于正方形来说,设定高同时就设定了宽,设定宽就同时设定了高,最后的面积并不 是等于我们设定的宽*高,而是等于最后一次设定的宽或者高的平方。具体代码样例如下:Rectangle.java1. packagecom.oo.java.principles.lsp;2. 3. /*4. *长方形5. */6. publicclassRectangle7. 8. protectedint_width;9. protectedint_height;10. 11. /*12. *设定宽13. *paramwidth14. */15. publicvoidsetWidth(intwidth)16. this._width=width;17. 18. 19. /*20. *设定高21. *paramheight22. */23. publicvoidsetHeight(intheight)24. this._height=height;25. 26. 27. /*28. *获取面积29. *return30. */31. publicintgetArea()32. returnthis._width*this._height;33. 34. Square.java1. packagecom.oo.java.principles.lsp;2. 3. /*4. *正方形5. */6. publicclassSquareextendsRectangle7. 8. /*9. *设定“宽”,与长方形不同的是:设定了正方形的宽,同时就设定了正方形的高10. */11. publicvoidsetWidth(intwidth)12. this._width=width;13. this._height=width;14. 15. 16. /*17. *设定“高”,与长方形不同的是:设定了正方形的高,同时就设定了正方形的宽18. */19. publicvoidsetHeight(intheight)20. this._width=height;21. this._height=height;22. 23. 24. UnitTester.java1. packagecom.oo.java.principles.lsp;2. 3. publicclassUnitTester4. 5. publicstaticvoidmain(Stringargs)6. Rectanglerectangle=newRectangle();7. rectangle.setWidth(4);8. rectangle.setHeight(5);9. 10. /如下assert判断为true11. assert(rectangle.getArea()=20);12. 13. rectangle=newSquare();14. rectangle.setWidth(4);15. rectangle.setHeight(5);16. 17. /如下assert判断为false,断言失败,抛出java.lang.AssertionError18. assert(rectangle.getArea()=20);19. 20. 21.上面这个样例同时也给出了一个判断子类是否符合LSP的取巧的方法,即:针对父类的单元测试用例,传入子类是否也能够测试通过。如果测试能够通过,则说明符合LSP原则,否则就说明不符合LSP原则.6. ISP原则 ISP,InterfaceSegregationPrinciple,中文翻译为“接口隔离原则”。和 DIP原则一样,ISP原则也是大名鼎鼎的Martin大师提出来的,他在1996年的C+Reporter发表 “TheInterfaceSegregationPrinciple”的文章详细阐述了ISP原则,并且在他的经典著作 AgileSoftwareDevelopment,Principles,Patterns(中文翻译为:敏捷软件开发:原则、模式与实 践)、Practices,an

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论