OO设计原则!
这是很多开发资源网站必备的一个栏目、专题、至少也要转载一篇放在自己的网站上的东西。所有的程序员,如果你不开发面向对象的程序也就罢了——反正你已经落伍很久了,如果你要想开发OO程序,而竟然没有把那些OO设计原则熟读背诵,搞得滚瓜烂熟。那么你就完了,一个公司面试你的时候,问你:“你对SRP的理解是怎么样的?”,而你居然不知道SRP是什么,那么这家公司你也就别想进去了。作为OO程序员的《旧约圣经》(设计模式自然是《新约圣经》)他怎么就会那么神圣呢?
介绍OO设计原则的文章很多,我在google上搜索了一下:“约有58,200项符合OO设计原则的查询结果”。真正能够介绍得透彻的,还真是没几个。正好我手边有一本Bob大叔的《UML for JAVA Programmers》那上面的介绍,在我看来,是最好的OO设计原则介绍之一了。另外一本不在手边的《敏捷软件开发 原则、模式与实践》也是Bob大叔的,还要详尽一些。如果要批判,自然要找这样的靶子来练!
1、单一职责原则(SRP)
一个类只能因为一个原因而改变。
“A class should have one, and only one, reason to change.”
这个原则何等的简单,又是何等的模糊呢?什么叫做一个原因呢?我们来看下面这个类:
java代码: |
class User{ private String name; private int age; public void setName(String name){ this.name=name; } public void setAge(int age){ this.age=age; } }
|
请问,这个类是不是违反了SRP原则呢?设置用户的名字与设置用户的年龄,是一个原因,还是两个原因呢?Bob大叔在自己的书里举了一个例子,说明了违反SRP原则的情况,一个Employee类,包含了计算工资和扣税金额、在磁盘上读写自己、进行XML格式的相互转换、并且能够打印自己到各种报表。我说拜托啊大叔!一个类里的方法多到如此惊人的程度,自然是违反了SRP原则,但是我们要为它瘦身,该瘦到什么程度呢?按照大叔继续给出的自己的答案,它把计算工资和扣税金额的两个功能留给了Employee,其他的分离了出去。这个答案正确吗?员工的工资和税收是自己算的?还是有一个“财务部”对象来计算的呢?且不说那么扫兴的事情,就看看那个类图里分离出来的那几个类:
EmployeeXMLConverter、EmployeeDatabase、TaxReport、EmployeeReport、PayrollReport。这些类还需要有自己的内部数据吗?请注意,他们事实上都是通过接受Employee对象的内部数据而工作的,换句话说,这些所谓的类,根本就不是什么类,只不过是一个个用Class关键字包裹起来的函数库!当我们看到一个臃肿的Employee类,被拆成6个各不相同的类之后,内心自然升起了“房子打扫干净之后的喜悦”。但是,且慢!灰尘到哪里去了呢?当我们把一个类拆成6个类之后,那个原本的类自然已经遵守了SRP原则,然后新诞生的5个类,是不是也该遵守SRP原则呢?如果我们不能将一个原则应用于整个系统设计中的所有的对象,仅仅像小孩打扫卫生一样,把灰尘扫到隔壁房间,这剩下的事情,谁来处理呢?
好吧,我们不要这么严厉,毕竟这只是一个原则,追问太深似乎并不合适。我只想再搞清楚几个问题:按照SRP原则,C++中是不是一律不应该出现多重继承呢?按照SPR原则,Java中的一个类是不是一律不应该既继承一个类,又实现一个对象呢?一个简单的POJO,被动态增强之类的办法,添加出来的新的持久化能力,是不是也是违反SRP原则的呢?归根结蒂,我的问题是:按照SPR原则,我那些剩下的,但是又必须要找地方写的代码,究竟应该写在哪里呢?
2、开放-封闭原则(OCP)
软件实体(类、模块、方法等)应该允许扩展,不允许修改。
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
这个原则倒是非常的清楚明白,你不能改已经写好的代码,而应该扩展已有的代码!如何做到这一点呢?Bob大叔举了一个经典的例子:个人认为这个例子说明的是一个使用接口,隔离相互耦合的类的通常做法。而且这个做法不应叫做OCP,而应该叫做DIP。查了一下c2.com里的OCP的解释:
In other words, (in an ideal world...) you should never need to change existing code or classes: All new functionality can be added by adding new subclasses and overriding methods, or by reusing existing code through delegation.
但是在Bob大叔的OCP解释中,这个原则的具体实现被偷换了概念,从“鼓励多使用继承”,变成了“鼓励面向接口编程”。为什么?因为继承式OCP实践已经被证明会带来相当多的副作用,而面向接口编程又如何呢?我们在讨论DIP的时候再详细讨论吧。
有一个在JavaEye的讨论的连接可以参考:对于OCP原则的困惑
3、里斯科夫替换原则(LSP)
子类型必须能够替代他们的基本类型。
“Subtype must be substitutable for their base types.”
对于这个问题,我都不用多说什么,只引用Bob大叔在c2上的一句话,以作为我的支持。
“I believe that LSP is falsely believed by some few to be a principle of OO design, when really it isn't.”
4、依赖关系倒置原则(DIP)
A.上层模块应该不依赖于下层模块,它们都依赖于抽象。
B.抽象不应该依赖于细节,细节应该依赖于抽象。
“A. High level modules should not depend upon low level modules. Both should depend upon abstractions. ”
“B. Abstractions should not depend upon details. Details should depend upon abstractions.”
Bob大叔还补充说明了一下,这个原则的意思就又变了:更确切的说,不要依赖易变的具体类。也就是说,不容易变的具体类,还是可以依赖的。那么,当我们开始一次系统开放的时候,那些类是易变的具体类呢?那些类是不易变的具体类呢?怎么才算是易变、怎么才算是不易变呢?我们来看看代码吧:
java代码: |
class A{ public void doA(){ } }
class B{ A a=new A(); a.doA(); }
|
按照DIP原则,Class B依赖于一个具体实现类Class A,因此是可耻的!最起码你应该写成这样:
java代码: |
interface A{ public void doA(){ } }
class AImpl implements A{ public void doA(){ } }
class B{ A a=new AImpl(); a.doA(); }
|
这样,AImpl和B都依赖于Interface A,很漂亮吧。还不够好,A a=new AImpl();还是很丑陋的,应该进一步隔离!A a=AFactory.createA();在AFactory里,再写那些见不得人的new AImpl();代码。然后呢?这还没完,更加Perfect的办法,是采用XML配置+动态IOC装配来得到一个A,这样,B就能够保证只知道这个世界上有一个Interface A,而不知道这个Interface A是从哪里来的了。这么做的理由是什么呢?有一个很吓人的理由:“如果A被B直接使用,那么对于A的任何改动,就会影响到B了。这就叫依赖,而这样的依赖会很危险!”
我们来看看这颇有说服力的话,漏洞何在?A的变化有两种情况,一种是只修改A中的方法的内部实现,无论是直接使用A还是使用Interface A的某一个实现,这时候B的代码都不用改。另一种是修改了方法的调用接口,如果是直接使用A的Class B,就需要修改相关的调用代码,而如果是使用接口的呢?就需要同时修改Class B和Interface A。这样看来,采用接口方式,反而需要修改更多的代码!这使用接口的好处何在?
5、接口隔离原则(ISP)
客户端不应该依赖于自己不用的方法。
“The dependency of one class to another one should depend on the smallest possible interface.”
这个我就不说了!因为这个原则和SPR原则是矛盾的!就像合成复用原则(CRP)与LSP原则矛盾一样。
关于这个批判,我昨天晚上只写了一半,今天算是虎头蛇尾的写完了。最后录一段Bob大叔的话,作为结尾:
什么时候应该运用这些原则呢?首先要给您一个痛苦的告诫,让所有系统在任何时候都完全遵循所有原则是不明智的。
运用这些原则的最好方式是有的放矢,而不是主动出击。在您第一次发现代码中有结构性的问题。或者第一次意识到某个模块受到另一个模块的改变的影响时,才应该来看看这些原则中是否有一条或者多条可以用来解决问题。
......
找到得分点的最佳办法是大量写单元测试。如果能够先写测试,再写要测试的代码,效果会更好。
让我来翻译一下上面的告诫。原则不是你可以用来预防问题的,而是当你已经陷入麻烦的时候,你可以停下来悔恨一下。至于解决之道,依然不是很清楚,因此,你需要写大量的单元测试。而且,大量的单元测试并不是帮你检查你的设计漏洞,而是帮你更真切的感受自己的设计是否正确。至于他究竟是不是正确,这就看个人自己的感觉了。更为惊人的是,在测试驱动开发的建议中,如何驱动开发的准则,竟然是循环的来自于OO设计原则的。
这样的OO设计原则,就像老爸老妈给我们的人生教诲:“你要做好人啊”,别的什么都没说。而且我们还遇到了话都说不清的糊涂爹娘,怎么才算好人,不清楚,怎么才算坏人呢?被警察抓了,肯定就是坏人了。至于如何才能做得更好?自己体会吧。
(未完待续)
分享到:
相关推荐
20210509-信达证券-化工行业:敲响轮胎消费时代的钟声!.pdf
【标题解析】:“图片购物搜索淘淘搜:敲响美丽说们的丧钟?”指的是新兴的电商搜索工具“淘淘搜”通过创新的图片购物搜索功能,对以美丽说、蘑菇街为代表的图片购物网站构成潜在的竞争威胁。 【描述概要】:文章...
化工行业:敲响轮胎消费时代的钟声!.pdf
《地球——我们的家园》是教育学生认识地球独特性和环保重要性的关键课程。这一课时的教学设计旨在通过多种教学策略,使学生对地球的唯一性有深入理解,提高他们对环境保护的认识,培养珍视地球家园的情感。 教学...
1. 教育资源:文档是一个针对六年级下册道德与法治课程的教学设计,具体为第4课“地球——我们的家园”。这表明教育资源正在关注环境教育,旨在培养学生的环保意识。 2. 知识目标:教学设计中包含了三个层次的目标...
化工行业:敲响轮胎消费时代的钟声!(30页).pdf
化工行业:敲响轮胎消费时代的钟声!(2021)(30页).pdf
提到的“丧钟为谁而鸣”,这句话源自约翰·多恩的诗句,意味着每个人的死亡都是所有人的损失,每个人都在为自己的死亡敲响丧钟。在这里,读者被提醒珍惜当下,因为生命无常,每一刻都可能是最后的时刻。同时,这也...
"地球——我们的家园 教案(教学设计)" 本教案旨在帮助六年级学生初步养成珍惜地球资源、保护地球环境的行为与习惯,了解地球是目前已知的惟一有生命存在的、适宜人类生存和发展的星球。通过教学,学生能初步归纳...
Window操作系统的诞生成就了微软帝国,同时也造就了PC时代的繁荣,然而如今,以Android和iPhone手机为代表的智能移动设备的发明与互联网云技术的兴起却敲响了PC时代的丧钟!这也预示着移动互联网时代(3G)已经来临。 ...
本课主题为“地球——我们的家园”,旨在教育六年级学生了解地球作为我们唯一的生存家园的重要性和环境问题的严重性。教学设计围绕两个主要部分展开:一是认识我们生存的家园,二是环境问题对人类生存的警示。 1. *...
这篇读后感主要围绕的是作者在疫情期间阅读《小学生时代》杂志后的感受。书中的内容虽然未具体提及,但从作者的描述中可以看出,这本书或许包含了对疫情的报道、对前线工作者的致敬,以及对生活的反思和希望的探讨。...
此外,课文中的另一关键句子“让那已经能够听到脚步声的21世纪,为战争敲响丧钟,让明天的世界真正成为充满阳光、鲜花和爱的人类家园!”则要求学生理解作者对未来和平世界的期盼,通过合作探究,理解这一愿景的深刻...
然而,以android和iphone手机为代表的智能移动设备的发明却敲响了pc时代的丧钟!移动互联网时代(3g时代)已经来临,谁会成为这些移动设备上的主宰?毫无疑问,它就是android——pc时代的windows! 移动互联网还是...
这篇资料是针对广东地区2015年高考英语大二轮总复习的一份完形填空专题训练,主题为“完形填空高分三原则——攻克记叙文”。资料中通过一个生动的故事来教授学生如何理解和解答完形填空中的记叙文题目。 文章讲述了...
《医疗废物管理的法律问题探析——以湖南某湖流域为例》 医疗废物管理是公共卫生领域中的重要环节,它涉及到环境保护、公众健康和社会稳定等多个层面。这篇研究主要以湖南某湖流域为背景,深入探讨了我国医疗废物...
《地球——我们的家园》是部编版六年级下册道德与法治课程中的第四课,旨在教育学生认识地球作为人类生存环境的重要性,理解人与自然之间的相互依存关系,以及环境问题的严重性,培养学生的环保意识和责任意识。...
移动应用研发是一个多阶段的过程,涉及从创意设计到产品推广再到效果评估的各个方面。在这个过程中,开发者会遇到许多挑战,尤其是在连接前端与后端数据时。传统上,移动应用的研发流程复杂且耗时,这限制了产品迭代...
今年,集团内部连续发生的重大安全事故为全体人员敲响了警钟,强调了安全发展的必要性。全国第五个“安全生产年”和第12个“安全生产月”的到来,进一步凸显了当前阶段对安全工作的紧迫性和重要性。 “安全第一,...
Android 应用开发揭秘 ...以Andorid和iphone手机为代表的智能移动设备的发明却敲响了PC时代的丧钟!移动互联网时代(3G 4G)已经来临,谁会成为这些移动设备的主宰?毫无疑问,它就是Android--PC时代的Windows!