单一职责原则
单一职责原则(Single Responsibility Principle,简称SRP)的英文原话是:there should nerver be moren than one reason for a class to change.意思是一个类,应当只有一个引起它变化的原因。即一个类应该只有一个职责。就一个类而言,应当值专注于做一件事且仅有一个引起变化的原因,这就是所谓的单一职责原则。该原则提出了对对象职责的一种理想期望,对象不应该承担太多职责,正如人不应该一心分为二用。唯有专注才能保证对象的高内聚;唯有单一,才能保证对象的细粒度。对象的高内聚与细粒度有利于对象的重用。当客户端需要该对象的某一职责时,却不得不将所有的职责都包含进来,从而造成冗余的代码。
单一职责原则还有利于对象的稳定,所谓“职责”,就是对象能够承担的责任,并以某种行为方式来执行。对象的职责总是要提供给其他对象进行调用的,从而形成对象与对象的协作,产生对象之间的依赖关系。类的职责越少,对象之间的依赖关系就越少,耦合度就减弱,受其他对象的约束和牵制就越少,从而保证了系统的可扩展性。因此,在单一职责原则中,也可以把“职责”定义为“变化的原因”。如果存在多个动机去改变一个类,那么这个类就具有多余一个类的职责。“变换的原因”只有实际发生时才有意义,可能预测到会有多个原因引起这个类的变化,但这仅仅是预测,并没有实际发生,那么这个类仍可看作单一职责原则,不需要分离职责。
单一职责原则并不是极端的要求我们只能为类定义一个职责,而是利用极端的表述方式重点强调,在定义对象职责时,必须考虑职责与对象之间的所属关系。职责必须恰如其分的表现对象的行为,而不至于破坏和谐与平衡的美感,甚至格格不入。换言之,该原则描述的单一职责原则指的是公开在外的与该对象紧密相关的一组职责。
单一职责原则的优点:
1.降低类的复杂性。
2.提高类的可读性。
3.提高代码的可维护性和复用性。
4.降低因变更而引起的风险。
里氏替换原则
里氏替换原则英文名为:Liskov Substitution Principle,简称LSP。
里氏替换原则定义
在面向对象的语言中,继承是必不可少的、优秀的语言机制,它的主要优点如下:
代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。
提高代码的可重用性。
提高代码的可扩展性。
提高产品或项目的开放性。
相应的,继承也存在缺点,主要体现在以下几个方面:
继承是侵入式的。只要有继承,就必须拥有父类的所有属性和方法。
降低代码的灵活性。子类必须拥有父类的属性和方法,使子类受到限制。
增强了耦合性。当父类的常量、变量和方法修改时,必须考虑子类的修改,这种修改可能造成大片的代码需要重构。
从整体上来看,继承的“利”大于“弊”,然而如何让继承中“利”的因素发挥最大作用,同时减少“弊”所带来的麻烦,这就需要引入“里氏替换原则”。
里氏替换原则有两种定义:
1.第一种定义
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of S,the behavior of P is unchanged when o1 is substituted for o2 then T is a subtype of S.
这个定义是最正宗的定义。它的意思是:如果对一个类型为S的对象o1,都有类型为T的对象o2,使得以S定义的所有程序P中所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T是类型S的子类型。
2.第二种定义
Function that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
第二个定义的意思是:所有引用基类的地方必须能透明地使用其子类对象。它清晰明确地说明只要父类能出现的地方子类就可以出现,而且将父类替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道这个类是父类还是子类;但是反过来则不可以,有子类的地方,父类未必就能适应。
里氏替换原则为良好的继承定义了一个规范,它包含四层含义:
1.子类必须完全实现父类的方法。
2.子类可以有自己的个性。
3.子类覆盖或实现父类的方法时输入参数可以被放大。
4.子类覆盖或实现父类的方法时输出结果可以被缩小。
体现里氏替换原则的设计模式有三种:策略模式,组合模式,代理模式。
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,简称DIP)原始定义是:Heigh level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻译过来包括三层含义:
1.高层模块不应该依赖低层模块,两者都依赖其抽象。
2.抽象不依赖细节。
3.细节应该依赖抽象。
传统的过程性系统的设计方法倾向于高层次的模块依赖于低层次的模块,抽象层次依赖于具体层次。“倒置原则”将这个错误的依赖关系倒置了过来,由此命名为“依赖倒置原则”。
在java语言中抽象就是指接口或抽象类,两者都是不能直接被实例化的。细节就是具体的实现类,实现类实现了接口或继承了抽象类,其特点是可以直接被实例化。
依赖倒置原则在java语言中的表现是:
1.模块间的依赖通过抽象产生,实现类之间不发生直接的依赖关系,其依赖关系时通过接口或抽象类产生的。
2.接口或抽象类不依赖于实现类,实现类依赖于接口或抽象类。
依赖倒置原则更加精确的定义就是“面向接口编程”--OOD(Object-Oriented Design)的精髓之一。依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,并提高代码的可读性和可维护性。依赖倒置原则是JavaBean、EJB和COM等组件设计模型背后的基本原则。
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,互不影响,实现模块间的松耦合。在项目中使用这个原则需要遵循以下几个规则:
1.每个类应该都具有接口或抽象类,或者同时具备抽象类和接口。这是依赖倒置的基本要求,接口和抽象类都是抽象的,有了抽象才可能有依赖倒置。
2.变量的表面类型尽量是接口或者抽象类(即声明的变量不要是子类,而是它的接口或者抽象类,然后子类的对象赋给这个接口变量)。
3.任何类都不应该从具体类派生。
4.尽量不要重写基类的方法。如果基类是一个抽象类,而且其方法已经实现了,子类尽量不要重写。类之间依赖的是抽象,重写了抽象方法,对依赖的稳定性会产生影响。
5.结合历史替换原则的使用。里氏替换原则支出父类出现的地方子类就可以出现,结合依赖倒置原则可以得出一个通俗的规则:接口负责定义抽象方法,并且声明与其他对象的依赖关系;抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
接口隔离原则
接口隔离原则英文名称是:Interface Segregation Principle,简称ISP。
定义
首先明确“接口的概念”。接口分为两种:
1.实例接口(Object Interface),在java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事务所具有的方法特征的描述,也称作一个“接口”,这仅是一种逻辑上的抽象。
2.类接口(Class Interface),是指在java中使用interface严格定义的接口。
针对这两种不同类型的“接口”,接口隔离原则的含义及表达方式都有所不同,接口隔离原则有如下两种定义:
1.Clients should not be found to depend upon interfaces that they don‘t use.
意思是:客户端不应该依赖它不需要的接口。
2.The dependency of one class to another one should depend on the smallest possible interface.
意思是:类间的依赖关系应该建立在最小的接口上。
接口隔离原则的具体的含义如下:
1.一个类对另外一个类的依赖性应当是建立在最小的接口上的。(当外界需要调用且只需要这个类的某种功能时,为这个类设计一个接口,让类继承这个接口,然后把接口公开出去)
2.一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。因此使用多个专门的接口比使用单一的总接口要好。
3.不应该强迫客户依赖于他们不用的方法。接口属于客户,不属于它所在的 类层次的结构。即不要强迫客户使用他们不用的方法,否则这些客户就会面临由于这里不适用的方法的改变所带来的问题。
接口隔离原则是对接口的定义,同时是对类的定义,应尽量使用原子接口或原子类,其中“原子”在实践中可以根据以下几条标准来衡量:
1.一个接口只对一个子模块或者业务逻辑进行服务。
2.只保留接口中业务逻辑需要的public方法。
3.尽量修改污染了的接口,若修改的风险较大,则可采用适配器模式进行转化处理。
4.接口设计应因项目而异,因环境而异,不能教条照搬。
接口隔离原则和其他的设计原则一样,都需要花费时间和经理来进行设计和筹划。但是它带来了设计的灵活性,并降低整个项目的风险,当业务变化时能够快速应付。在设计接口时应根据经验和常识决定接口的粒度大小。如果太小,将会导致接口数量剧增,给开发带来难度;如果接口粒度太大,灵活性降低,将无法提供定制服务,给项目带来无法预计的风险。
迪米特法则
迪米特法则英文名称是Law of Demeter,简称LOD。
定义
迪米特法则又叫最少知识原则(Least Knowledge Principle,简写为LKP),意思是:一个对象应应当对其他对象尽可能少的了解。迪米特法则最初是用来作为面向对象系统设计风格的一种法则,于1987年由Ian Holland在美国东北大学为一个叫迪米特的项目设计提出的,因此叫做迪米特法则。这条法则实际上是很多著名系统,比如火星登录软件系统,木星的欧罗巴卫星轨道飞船软件系统的指导设计原则。
迪米特法则不同于其他的OO设计原则,它具有很多种表达方式,其中最具代表性的是以下几种表述:
1.只与你直接的朋友们通信(Only talk to your immediate friends)。
2.不要跟“陌生人”说话。(Don‘t talk to strangers)。
3.每一个软件单位对其他的单位都只有最少的了解,这些了解仅局限于那些与本单位密切相关的软件单位。
按照迪米特法则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
迪米特法则的核心观念就是类之间的解耦、弱耦合,只有弱耦合了以后,类的复用率才可以更高。
在设计模式中,对迪米特法则进行应用的设计模式有如下两种:外观者模式。中介者模式。
开闭原则
开闭原则的英文名称是Open-Closed Principle,常缩写为OCP。
定义
开闭原则英文原文是:Software entities should be open for extension,but closed for modification.
意思是:一个软件实体应当对拓展开放,对修改关闭。
这个原则说的是,在设计一个模板的时候,应当是这个模板可以在不被修改的前提下被扩展。即应当可以在不必修改源代码的情况下改变这个模块的行为。
在面向对象的编程中,开闭原则是最基础的原则,起到总的指导作用,其他原则(单一职责、里氏替换、依赖倒置、接口隔离、迪米特)都是开闭原则的具体形态,即其他原则都是开闭原则的手段和工具。开闭原则的重要性可以通过以下几个方面体现:
1.开闭原则提高复用性。在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。代码粒度越小,被复用的可能性就越大,避免相同的逻辑重复增加。开闭原则的设计保证系统是一个在高层次上实现了复用的系统。
2.开闭原则提高可维护性。一个软件投产后,维护人员的工作不仅仅是对数据进行维护,还有可能对程序进行扩展,就是一个扩展类,而不是修改一个类。开闭原则要求对已有软件模块,特别是最重要的抽象层模块不能在进行修改,这就使变化中的软件系统有一定的稳定性和延续性,便于系统的维护。
3.开闭原则提高灵活性。所有的软件系统都有一个共同的性质,即用户对系统的需求都会随时间的推移发生变化。在软件系统面临新的需求时,系统的设计必须是稳定的。开闭原则可以通过扩展已有的软件系统,提供新的行为,快速应对变化,以满足对软件的新的需求,是变化中的软件系统有一定的适应性和灵活性。
4.开闭原则易于测试。测试是软件开发过程中必不可少的一个环节。测试代码不仅要保证逻辑的正确性,还要保证程序在苛刻条件(高压力、异常、错误)下不产生“有毒代码(Poisonous Code)”因此当有变化提出时,原有的健壮的代码尽量不要修改,而是通过扩展来实现。否则就需要把原有的测试过程回笼一遍,需要进行单元测试、功能测试、集成测试、甚至验收测试。开闭原则的使用,保证软件是通过扩展来实现业务逻辑的变化,而不是修改。因此,对于新增加的类,只需新增对应的测试类,编写对应的测试方法,只要保证新增加的类是正确的就可以了。