单一责任原则[Single Responsibility Principle--SRP]
定义:There should never be more than one reason for a class to change.
意译:不应该有超过一个因素引起类的改变
优点:
一个类负责一个职责,复杂度降低
提高可读性,可维护性
如果类能很好的遵循单一责任原则,将会显著减少变更引起的联动变化,降低了变更引发的风险
缺点:
因为代码颗粒度变细,将可能引起类膨胀
理解:"定义描述很简单,责任划分很困难",这个应该是比较大众的对这一原则的看法,"责任"并不是一个量化的指标,"单一责任"也完全是依靠人为划分的,而影响这一行为的又有很多因素,如何在相应的项目环境中准确的定义出"单一责任"的概念是我们需要一直努力学习的,这不是一触而就的,需要大量的设计经验,但起码,我们应当做到:面向接口编程,所以,尽管在很多条件下类不可能做到单一责任,但我们应该尽可能的把接口设计成符合单一责任原则...
示例:考虑这样一个场景,一个责任混杂的工具类,其中方法A依赖于外部类库L,那么,当把这个工具类作为模块提供给外部模块使用时,那些不需要用到方法A的的模块也将必须要依赖外部类库L才行,使用单一责任原则,隔离出包含方法A的职责封装成另一个工具类,即可以解决问题.
里氏替换原则[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 T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
意译1:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型.
定义2:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
意译2:所有使用基类的地方必须能透明地使用其派生类.
优点:
代码共享,减少类的创建
提高代码的重用性
提高代码的可扩展性
缺点:
继承是入侵的
增强了耦合
理解:从优缺点可以看出,使用里氏替换原则的优缺点就是继承的优缺点,因为这一原则的目标就是为了放大继承体系的优点,缩小其缺点,它的使用前提就是继承...它包含了四层含义:①子类必须完全实现父类的方法;②子类可以有个性[但我们应当尽可能的减少子类的个性];③子类重载继承自父类的方法时,输入参数(方法形参)可以被放大;④子类重写方法时,输出结果(返回值)可以被缩小.
示例:使用继承体系时遵循里氏替换原则能帮我们避免一些隐藏在其中的隐患,以下代码了违背第三层含义
public class Father { public void doSth(Map map){ System.out.println("Father"); } } public class Son extends Father{ public void doSth(HashMap map){ //子类缩小了输入参数 System.out.println("Son"); } } public static void main(String[] args) { Father temp = new Father(); //Father Son temp = new Son(); //Son HashMap map = new HashMap(); temp.doSth(map); }
可以看到,当我们用子类替换父类时,代码逻辑发生了变化,我们应当避免类似的问题发生.
依赖倒置原则[Dependence Inversion Principle--DIP]
定义:High 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.
意译:高层模块不应该依赖低层模块,两者都应该依赖于抽象,抽象不应该依赖细节,细节应该依赖抽象.
优点:
降低类间耦合
提高系统稳定性
提高代码的可读性和可维护性
利于并行开发
缺点:
项目中的类文件肯定会增多(有了上述的优点,这点代价都不愿意付出么?)
理解:不要被名字唬住,这也许只是为了显示出高端(just kidding)...这一原则的核心思想就是"面向接口编程",一切皆依赖抽象,抽象是对实现的约束,同时也是与外部沟通的约束,当我们使用抽象传递依赖[构造器注入,Setter注入,接口注入],搭建系统骨骼,其稳定性必定要远远强于直接使用具体实现的方式,而要使代码遵循该原则,可以通过以下几点:①每个类都尽量有接口或抽象类;②变量的表面类型尽量被声明为接口或抽象类③任何类都尽可能避免派生自具体类;④子类尽量不要重写父类已经实现的方法;⑤结合里氏替换原则使用.
示例:一个只能开BWM的老司机.....
public class Driver { // 司机的主要职责就是驾驶汽车 public void drive(BMW bmw) { bmw.run(); } } public class Benz { // 汽车肯定会跑 public void run() { System.out.println("奔驰汽车开始运行..."); } } public class BMW { // 宝马车当然也可以开动了 public void run() { System.out.println("宝马汽车开始运行..."); } } public static void main(String[] args) { Driver zhangSan = new Driver(); BMW bmw = new BMW(); zhangSan.drive(bmw); }
本例中有C驾照的老司机竟然只能开宝马,而不能开奔驰,这显然是不科学的....我们对程序做如下修改以满足依赖倒置原则
public class Driver { // 司机的主要职责就是驾驶汽车 public void drive(ICar car) { car.run(); } } public class Benz implements ICar{ // 汽车肯定会跑 public void run() { System.out.println("奔驰汽车开始运行..."); } } public class BMW implements ICar{ // 宝马车当然也可以开动了 public void run() { System.out.println("宝马汽车开始运行..."); } } public static void main(String[] args) { Driver zhangSan = new Driver(); zhangSan.drive(new BMW()); zhangSan.drive(new Benz()); }
这才是真正的老司机,不管你什么车,信手拈来~~~
接口隔离原则[Interface Segregation Principle--ISP]
定义1:Clients should not be forced to depend upon interfaces that they don't use.
意译1:客户端不应该被强迫依赖于它所不需要的接口.
定义2:The dependency of one class to another one should depend on the smallest possible interface.
意译2:类与类之间的依赖应该建立在尽可能小的接口上.
优点:
接口细化后,可自由"组合",易于应对变化,提高了维护性和扩展性
缺点:
会增加代码结构复杂度
理解:这一原则和单一责任原则有些相似,但我们应当了解它们的意图是不同的:单一责任原则的关注点在于职责,这是从业务逻辑上的划分[难],而接口隔离原则的关注点是接口中的方法数量要尽可能的少[易];另一方面,严格来说单一责任原则的原始定义是针对类,对接口和方法的原则应用是其扩展形式.由于存在上述的相似性,我们在设计接口时可能会遇见只能符合两者中的一个的情况(鱼与熊掌不可兼得),这时候全凭个人的设计偏好了,我更愿意优先遵循单一责任原则....下面说说它具体包含的四层含义:①接口尽量小;②高内聚;③定制服务(通过将细小的接口自由组合来为不同的个体提供定制服务);④设计不能过度(很显然,一个接口一个方法肯定能满足接口隔离原则,但是一样显然的,你绝不会那么设计~~~).
示例:考虑如下场景,警务系统有个内部信息查询平台,它提供给客户两个接口,查询基本信息,查询秘密信息,前者面向整个警务系统开放,后者只针对领导层
我们来看第一种实现方式:
public interface IPersonInfoService { public void gainBaseInfo(); public void gainSecretInfo(); } public class PersonInfoService implements IPersonInfoService { @Override public void gainBaseInfo() { System.out.println("调用DAO层获取基本信息接口"); } @Override public void gainSecretInfo() { System.out.println("调用DAO层获取秘密信息接口"); } }
可以看到,我们提供了一个完整的接口给外部调用模块,然后通过口头约束来告诉外部模块能不能查询秘密信息
然后是第二种方式:
public interface IBaseInfoService { public void gainBaseInfo(); } public interface ISecretInfoService { public void gainSecretInfo(); } public class BaseInfoService implements IBaseInfoService { @Override public void gainBaseInfo() { System.out.println("调用DAO层获取基本信息接口"); } } public class WholeInfoService implements IBaseInfoService, ISecretInfoService { @Override public void gainBaseInfo() { System.out.println("调用DAO层获取基本信息接口"); } @Override public void gainSecretInfo() { System.out.println("调用DAO层获取秘密信息接口"); } }
这里我们将查询接口拆分成两个独立的接口,实现类通过自由拼装所需要实现的接口来为外部两个调用模块提供单独的定制服务
*现实场景中的业务远远不是像示例所描述的那般简单的,我们可以想象,在具体场景中,这两种方式的差距是巨大的.
最少知识原则[Least Knowlegde Principle--LKP]
定义:Only talk to your immediate friends.
意译:只和你的朋友直接交谈.(一个对象应该对其他对象有最少的了解)
优点:
降低耦合,提高系统灵活性
缺点:
产生的中介类会导致系统结构复杂度增加
理解:要理解这个原则,首先需要解释出现在定义中的"朋友"一词的含义,它指类的成员变量,输入参数,返回值.言归正传,最少知识原则包含了四层含义:①只和朋友交谈(类中不应该出现对"朋友"之外的访问,JAVA,框架 API除外,毕竟一般情况下它们是不变的);②朋友也要适度保持距离(尽量减少public的使用);③是自己的就是自己的(如果一个方法放在本类中,既不增加类间的关系,也不会对本类产生负面影响,那就放在本类中);④减少Serializable的使用....另外,需要了解的是,这货还有另一个名字:"迪米特法则".
示例:体育老师:"体育委员,你去把女生清一下",体育委员:"哎呀,亲哪个".....
我们来看实现:
public class Teacher { // 老师对学生发布命令,清一下女生 public void commond(GroupLeader groupLeader) { List listGirls = new ArrayList(); // 初始化女生 for (int i = 0; i < 20; i++) { listGirls.add(new Girl()); } // 告诉体育委员开始执行清查任务 groupLeader.countGirls(listGirls); } } public class GroupLeader { // 清查女生数量 public void countGirls(List<Girl> listGirls) { System.out.println("女生数量是:" + listGirls.size()); } } public class Girl { } public static void main(String[] args) { Teacher teacher = new Teacher(); // 老师发布命令 teacher.commond(new GroupLeader()); }
观察示例代码,发现Teacher类中包含了对非朋友类Girl的引用,这意味着,如果类Girl发生改变,将有可能影响到Teacher类,这样的代码无疑加大了变更所带来的风险,我们应该只和朋友交谈,对代码做出修改:
public class Teacher { // 老师对学生发布命令,清一下女生 public void commond(GroupLeader groupLeader) { // 告诉体育委员开始执行清查任务 groupLeader.countGirls(); } } public class GroupLeader { private List<Girl> listGirls; // 传递全班的女生进来 public GroupLeader(List<Girl> _listGirls) { this.listGirls = _listGirls; } // 清查女生数量 public void countGirls() { System.out.println("女生数量是:" + this.listGirls.size()); } } public class Girl { } public static void main(String[] args) { // 产生一个女生群体 List<Girl> listGirls = new ArrayList<Girl>(); // 初始化女生 for (int i = 0; i < 20; i++) { listGirls.add(new Girl()); } Teacher teacher = new Teacher(); // 老师发布命令 teacher.commond(new GroupLeader(listGirls)); }
通过上述修改,我们解除了Teacher类对陌生类Girl的依赖,降低了系统的耦合度,使得变更引起的风险降低.
开放关闭原则[Open Closed Principle--OCP]
定义:Software entities like classes,modules and functions should be open for extension but closed for modifications.
意译:软件实体比如类,模块,方法应该对扩展开放对修改关闭.
理解:开放关闭原则是软件开发最基础的原则,同时也是最根本的原则,它就像一个抽象类,而上述五大原则就像是它的具体实现.我们对开闭原则的遵循程度,直接反应了系统的稳定性,灵活性.它指导我们用抽象来搭建系统框架,用实现来扩展细节.它是"总则".
总结
上述所有的设计原则都是一种编程建言,它们都是为了开发出高稳定性,易于扩展维护的项目产品这一目标而建议采用的设计思想,而不是教条(这也是上述第五条采用"最少知识原则"而不使用另一个名字"迪米特法则"的原因),它们并没有要求我们规规矩矩的严格按照它们的定义来编写代码,事实上,在实际的开发中,我们也很难(其实我更相信不可能,因为它们本身的定义也并非是量化的指标)做到完全遵循六大设计原则.当我们仔细回忆上述法则的描述,不难发现其实很多情况下它们都要求我们细化代码颗粒度以到达代码复用,进而实现系统的灵活性,然而细化代码是有代价的,它将使得代码复杂度增加,所以,我们再实现系统灵活性的同时,也在加大代码结构复杂度,而找到其中的平衡点,才是设计原则真正希望我们做到的,我们应当始终了解:设计原则,设计模式都只是手段,开发出高稳定性,易于扩展维护的项目产品才是我们的最终目标,如果你有信心自己开发系统具备高稳定性,又易于扩展维护,那么你完全可以不懂设计原则,设计模式(如果你真的能在这样的前提下完成这样的系统,那么,也仅仅是你不知道你使用了设计原则/模式,而它们实际上必定已经充斥再你的代码中~~~,只能说,你天生是设计大师,孩子,想想就算了),缺少它们可能仅仅只是让你不能在同事中装逼~~~~而已
相关推荐
http://www.uml.org.cn/sjms/201211023.asp
http://my.oschina.net/u/1047712/blog/150173
<<HeadFirst 设计模式>>,<<设计模式之禅>>,<<设计模式 - 可复用面向对象软件的基础>>
备注
接口并不仅仅指Interface,在设计模式中接口往往指代超类型,抽象...