一、单一职责原则(Single Responsibility Principe,SRP)
1.1单一职责原则的定义
1)定义:在软件系统中,一个类只负责一个功能领域中的相应职责。
2)另一种定义方式如下:就一个类而言,应该仅有一个引起它变化的原因。
1.2对可变性的封装原则
一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小。而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
类的职责主要包过两方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
二、开闭原则(Open-Closed Principle,OCP)
2.1开闭原则的定义
一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
2.2开闭原则分析
在开闭原则的定义中,软件实体可以指一个软件模块,一个由多个类组成的局部结构或一个独立的类。
抽象化是开闭原则的关键。
绝大部分的设计模块都符合开闭原则,在对每一个模式进行优缺点评价时都会以开闭原则作为一个重要的评价依据,以判断基于该模式设计的系统是否具备良好的灵活性和可扩展性。
三、里氏代换原则(Liskov Substitution Principle,LSP)
3.1里氏代换原则定义
1)如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类型T的子类。
2)所有引用基类(父类)的地方必须能透明地使用其子类的对象。
3.2里氏代换原则分析
里氏代换原则可以通俗表述为:在软件中如果能使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
四、依赖倒转原则(dependenceinversion principle, DIP)
4.1概念
依赖倒转原则就是要依赖于抽象,不要依赖于实现。(Abstractionsshould not depend upon details. Details should depend uponabstractions.)要针对接口编程,不要针对实现编程。(Program to aninterface, not an implementation.)
也就是说应当使用接口和抽象类进行变量类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。而不要用具体类进行变量的类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。要保证做到这一点,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法。
传统的过程性系统的设计办法倾向于使高层次的模块依赖于低层次的模块,抽象层次依赖于具体层次。倒转原则就是把这个错误的依赖关系倒转过来。
面向对象设计的重要原则是创建抽象化,并且从抽象化导出具体化,具体化给出不同的实现。继承关系就是一种从抽象化到具体化的导出。
抽象层包含的应该是应用系统的商务逻辑和宏观的、对整个系统来说重要的战略性决定,是必然性的体现。具体层次含有的是一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。具体层次的代码是经常变动的,不能避免出现错误。
从复用的角度来说,高层次的模块是应当复用的,而且是复用的重点,因为它含有一个应用系统最重要的宏观商务逻辑,是较为稳定的。而在传统的过程性设计中,复用则侧重于具体层次模块的复用。
依赖倒转原则则是对传统的过程性设计方法的“倒转”,是高层次模块复用及其可维护性的有效规范。
特例:对象的创建过程是违背“开—闭”原则以及依赖倒转原则的,但通过工厂模式,能很好地解决对象创建过程中的依赖倒转问题。
4.2关系
“开-闭”原则与依赖倒转原则是目标和手段的关系。如果说开闭原则是目标,依赖倒转原则是到达"开闭"原则的手段。如果要达到最好的"开闭"原则,就要尽量的遵守依赖倒转原则,依赖倒转原则是对"抽象化"的最好规范。
里氏代换原则是依赖倒转原则的基础,依赖倒转原则是里氏代换原则的重要补充。
4.3耦合(或者依赖)关系的种类:
零耦合(NilCoupling)关系:两个类没有耦合关系
具体耦合(ConcreteCoupling)关系:发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成。
抽象耦合(AbstractCoupling)关系:发生在一个具体类和一个抽象类(或接口)之间,使两个必须发生关系的类之间存有最大的灵活性。
五、接口隔离原则(Interface Segregation Principle,ISP)
5.1接口隔离原则定义
1)客户端不应该依赖那些它不需要的接口
2)一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
5.2接口隔离原则分析
接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
(1)一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做“角色隔离原则”。
(2)接口仅仅提供客户端需要的行为。即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
可以在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为。
六、合成/聚合复用原则(Composite/AggregateReusePrinciple或CARP)
6.1概念
定义:在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的。
应首先使用合成/聚合,合成/聚合则使系统灵活,其次才考虑继承,达到复用的目的。而使用继承时,要严格遵循里氏代换原则。有效地使用继承会有助于对问题的理解,降低复杂度,而滥用继承会增加系统构建、维护时的难度及系统的复杂度。
如果两个类是“Has-a”关系应使用合成、聚合,如果是“Is-a”关系可使用继承。"Is-A"是严格的分类学意义上定义,意思是一个类是另一个类的"一种"。而"Has-A"则不同,它表示某一个角色具有某一项责任。
6.2什么是合成?什么是聚合?
合成(Composition)和聚合(Aggregation)都是关联(Association)的特殊种类。
聚合表示整体和部分的关系,表示“拥有”。如奔驰S360汽车,对奔驰S360引擎、奔驰S360轮胎的关系是聚合关系,离开了奔驰S360汽车,引擎、轮胎就失去了存在的意义。在设计中,聚合不应该频繁出现,这样会增大设计的耦合度。
合成则是一种更强的“拥有”,部分和整体的生命周期一样。合成的新的对象完全支配其组成部分,包括它们的创建和湮灭等。一个合成关系的成分对象是不能与另一个合成关系共享的。
换句话说,合成是值的聚合(Aggregation byValue),而一般说的聚合是引用的聚合(Aggregation byReference)。
明白了合成和聚合关系,再来理解合成/聚合原则应该就清楚了,要避免在系统设计中出现,一个类的继承层次超过3层,则需考虑重构代码,或者重新设计结构。当然最好的办法就是考虑使用合成/聚合原则。
6.3通过合成/聚合的优缺点
优点:
1)新对象存取成分对象的唯一方法是通过成分对象的接口。
2)这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。
3) 这种复用支持包装。
4) 这种复用所需的依赖较少。
5)每一个新的类可以将焦点集中在一个任务上。
6)这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。
7)作为复用手段可以应用到几乎任何环境中去。
缺点:就是系统中会有较多的对象需要管理。
6.4通过继承来进行复用的优缺点
优点:
新的实现较为容易,因为超类的大部分功能可以通过继承的关系自动进入子类。
修改和扩展继承而来的实现较为容易。
缺点:
继承复用破坏包装,因为继承将超类的实现细节暴露给子类。由于超类的内部细节常常是对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。
如果超类发生改变,那么子类的实现也不得不发生改变。
从超类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。
继承只能在有限的环境中使用。
七、 迪米特法则(Lawof Demeter,LoD)
7.1迪米特法则定义
定义:一个软件实体应当尽可能少的与其他实体发生相互作用。
这样,当一个模块修改时,就会尽量少的影响其他的模块。扩展会相对容易。
这是对软件实体之间通信的限制。它要求限制软件实体之间通信的宽度和深度。
7.2迪米特法则的其他表述:
1)只与你直接的朋友们通信。
2)不要跟“陌生人”说话。
3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
7.3狭义的迪米特法则
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
朋友圈的确定
“朋友”条件:
1)当前对象本身(this)
2)以参量形式传入到当前对象方法中的对象
3)当前对象的实例变量直接引用的对象
4)当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友
5)当前对象所创建的对象
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”;否则就是“陌生人”。
缺点:会在系统里造出大量的小方法,散落在系统的各个角落。
与依赖倒转原则互补使用
7.4狭义的迪米特法则的缺点:
在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。
遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
7.5广义的迪米特法则
指对对象之间的信息流量,流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发,优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一块模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。