一、单一职责原则(SRP)就一个类而言,应该仅有一个引起它变化的原因。
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。
测试驱动的开发实践常常会在设计出现臭味之前就迫使我们分离职责。
二、开闭原则(OCP)软件实体(类、模块、函数)应该是可扩展的,但是不可修改的。
也就是说:对于扩展是开放的,对于更改是封闭的。
怎样可能在不改动模块源代码的情况下去更改它的行为呢?怎样才能在无需对模块进行改动的情况下就改变它的功能呢?关键是抽象!因此在进行面向对象设计时要尽量考虑接口封装机制、抽象机制和多态技术。
该原则同样适合于非面向对象设计的方法,是软件工程设计方法的重要原则之一。
三、替换原则(LSP)子类应当可以替换父类并出现在父类能够出现的任何地方。
这个原则是Liskov于1987年提出的设计原则。
它同样可以从Bertrand Meyer 的DBC (Design by Contract〔基于契约设计〕) 的概念推出。
四、依赖倒置原则(DIP)1、高层模块不应该依赖于低层模块。
二者都应该依赖于抽象。
2、抽象不应该依赖于细节。
细节应该依赖于抽象。
在进行业务设计时,与特定业务有关的依赖关系应该尽量依赖接口和抽象类,而不是依赖于具体类。
具体类只负责相关业务的实现,修改具体类不影响与特定业务有关的依赖关系。
在结构化设计中,我们可以看到底层的模块是对高层抽象模块的实现(高层抽象模块通过调用底层模块),这说明,抽象的模块要依赖具体实现相关的模块,底层模块的具体实现发生变动时将会严重影响高层抽象的模块,显然这是结构化方法的一个"硬伤"。
面向对象方法的依赖关系刚好相反,具体实现类依赖于抽象类和接口。
五、接口分离原则(ISP)采用多个与特定客户类有关的接口比采用一个通用的涵盖多个业务方法的接口要好。
ISP原则是另外一个支持诸如COM等组件化的使能技术。
缺少ISP,组件、类的可用性和移植性将大打折扣。
这个原则的本质相当简单。
如果你拥有一个针对多个客户的类,为每一个客户创建特定业务接口,然后使该客户类继承多个特定业务接口将比直接加载客户所需所有方法有效。
以上五个原则是面向对象中常常用到的原则。
此外,除上述五原则外,还有一些常用的经验诸如类结构层次以三到四层为宜、类的职责明确化(一个类对应一个具体职责)等可供我们在进行面向对象设计参考。
但就上面的几个原则看来,我们看到这些类在几何分布上呈现树型拓扑的关系,这是一种良好、开放式的线性关系、具有较低的设计复杂度。
一般说来,在软件设计中我们应当尽量避免出现带有闭包、循环的设计关系,它们反映的是较大的耦合度和设计复杂化。
面向对象之代码复用规则1、对接口编程"对接口编程"是面向对象设计(OOD)的第一个基本原则。
它的含义是:使用接口和同类型的组件通讯,即,对于所有完成相同功能的组件,应该抽象出一个接口,它们都实现该接口。
具体到JAVA中,可以是接口,或者是抽象类,所有完成相同功能的组件都实现该接口,或者从该抽象类继承。
尽量使用接口。
接口只是对象打交道的入口,只有具有继承关系才使用抽象类。
2、优先使用对象组合,而不是类继承"优先使用对象组合,而不是类继承"是面向对象设计的第二个原则。
并不是说继承不重要,而是因为每个学习OOP的人都知道OO的基本特性之一就是继承,以至于继承已经被滥用了,而对象组合技术往往被忽视了。
只有有现实生活中的父子关系才使用继承。
相关的设计模式有:Bridge、Composite、Decorator、Observer、Strategy等。
3、将可变的部分和不可变的部分分离"将可变的部分和不可变的部分分离"是面向对象设计的第三个原则。
如果使用继承的复用技术,我们可以在抽象基类中定义好不可变的部分,而由其子类去具体实现可变的部分,不可变的部分不需要重复定义,而且便于维护。
如果使用对象组合的复用技术,我们可以定义好不可变的部分,而可变的部分可以由不同的组件实现,根据需要,在运行时动态配置。
这样,我们就有更多的时间关注可变的部分。
对于对象组合技术而言,每个组件只完成相对较小的功能,相互之间耦合比较松散,复用率较高,通过组合,就能获得新的功能。
4、减少方法的长度通常,我们的方法应该只有尽量少的几行,太长的方法会难以理解,而且,如果方法太长,则应该重新设计。
对此,可以总结为以下原则:三十秒原则:如果另一个程序员无法在三十秒之内了解你的函数做了什么(What),如何做(How)以及为什么要这样做(Why),那就说明你的代码是难以维护的,必须得到提高;一屏原则:如果一个函数的代码长度超过一个屏幕,那么或许这个函数太长了,应该拆分成更小的子函数;一行代码尽量简短,并且保证一行代码只做一件事那种看似技巧性的冗长代码只会增加代码维护的难度。
5、消除case / if语句要尽量避免在代码中出现判断语句,来测试一个对象是否某个特定类的实例。
通常,如果你需要这么做,那么,重新设计可能会有所帮助。
我在工作中遇到这样的一个问题:我们在使用JAVA做XML解析时,对每个标签映射了一个JAVA类,采用SAX(简单的XML接口API:Simple API for XML)模型。
结果,代码中反复出现了大量的判断语句,来测试当前的标签类型。
为此,我们重新设计了DTD(文档类型定义:Document Type Definition),为每个标签增加了一个固定的属性:classname,而且重新设计了每个标签映射的JAVA类的接口,统一了每个对象的操作:addElement(Element aElement); //增加子元素addAttribute(String attName, String attValue); //增加属性;则彻底消除了所有的测试当前的标签类型的判断语句。
每个对象通过Class.forName(aElement.attributes.getAttribute("classname")).newInstence(); 动态创建,6、类层次的最高层应该是抽象类。
在许多情况下,提供一个抽象基类有利做特性化扩展。
由于在抽象基类中,大部分的功能和行为已经定义好,使我们更容易理解接口设计者的意图是什么。
由于JAVA不允许"多继承",从一个抽象基类继承,就无法再从其它基类继承了。
所以,提供一个抽象接口(interface)是个好主意,一个类可以实现多个接口,从而模拟实现了"多继承",为类的设计提供了更大的灵活性。
7、尽量减少对变量的直接访问。
对数据的封装原则应该规范化,不要把一个类的属性暴露给其它类,而是应该通过访问方法去保护他们,这有利于避免产生波纹效应。
如果某个属性的名字改变,你只需要修改它的访问方法,而不是修改所有相关的代码。
8、拆分过大的类。
如果一个类有太多的方法(超过50个),那么它可能要做的工作太多,我们应该试着将它的功能拆分到不同的类中。
9、作用截然不同的对象应该拆分。
在构建的过程中,你有时会遇到这样的问题:对同样的数据,有不同的视图。
某些属性描述的是数据结构怎样生成,而某些属性描述的是数据结构本身。
最好将这两个视图拆分到不同的类中,从类名上就可以区分出不同视图的作用。
类的域、方法也应该有同样的考虑!如LinkedList和ListNode◆SRP,单一职责原则,一个类应该有且只有一个改变的理由。
◆OCP,开放封闭原则,一个类对扩展开放,而对修改闭合。
◆LSP,替换原则,所有用基类的地方都可以用派生类来替换。
◆DIP,依赖倒置原则,依赖于抽象而不是实现。
◆ISP,接口隔离原则,客户只要关注它们所需的接口。
接口尽可能单一,最小化,不要视图抽象一个包含很多功能的接口。
六十一条面向对象分析设计的经验原则你不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。
但你应当把这些原则看成警铃,若违背了其中的一条,那么警铃就会响起。
(1)所有数据都应该隐藏在所在的类的内部。
(2)类的使用者必须依赖类的共有接口,但类不能依赖它的使用者。
(3)尽量减少类的协议中的消息。
(4)实现所有类都理解的最基本公有接口。
如类对象toString而不应该自定义其他接口(5)不要把实现细节放到类的公有接口中。
公有函数是对外接口,具体实现应该尽量用私有函数做。
(6)不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。
(7)类之间应该零耦合,或者只有导出耦合关系。
也即,一个类要么同另一个类毫无关系,要么只使用另一个类的公有接口中的操作。
(8)一个类应该只表示一个关键抽象。
(Java不允许多重继承,解决了这个问题)包中的所有类对于同一类性质的变化应该是共同封闭的。
一个变化若对一个包影响,则将对包中的所有类产生影响,而对其他的包不造成任何影响.(9)把相关的数据和行为集中放置。
设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。
这种类型的行为暗示着这条经验原则被违反了。
(10)把不相关的信息放在另一个类中(也即:互不沟通的行为)朝着稳定的方向进行依赖.(11)确保你为之建模的抽象概念是类,而不只是对象扮演的角色。
(12)在水平方向上尽可能统一地分布系统功能,也即:按照设计,顶层类应当统一地共享工作。
(13)在你的系统中不要创建全能类/对象。
对名字包含Driver、Manager、System、Susystem 的类要特别多加小心。
规划一个接口而不是实现一个接口。
类对象实现的功能要单一。
(14)对公共接口中定义了大量访问方法的类多加小心。
大量访问方法意味着相关数据和行为没有集中存放。
(15)对包含太多互不沟通的行为的类多加小心。
(16)在由同用户界面交互的面向对象模型构成的应用程序中,模型不应该依赖于界面,界面则应当依赖于模型。
(17)尽可能地按照现实世界建模(我们常常为了遵守系统功能分布原则、避免全能类原则以及集中放置相关数据和行为的原则而违背这条原则) 。
(18)从你的设计中去除不需要的类。
(19)去除系统外的类。
系统外的类的特点是,抽象地看它们只往系统领域发送消息但并不接受系统领域内其他类发出的消息。
(20)不要把操作变成类。
质疑任何名字是动词或者派生自动词的类,特别是只有一个有意义行为的类。
考虑一下那个有意义的行为是否应当迁移到已经存在或者尚未发现的某个类中。
(21)我们在创建应用程序的分析模型时常常引入代理类。
在设计阶段,我们常会发现很多代理没有用的,应当去除。
(22)尽量减少类的协作者的数量。
一个类用到的其他类的数目应当尽量少。
(23)尽量减少类和协作者之间传递的消息的数量。
(24)尽量减少类和协作者之间的协作量,也即:减少类和协作者之间传递的不同消息的数量。