1. 单一职责原则(SRP)
(1)概念
单一职责原则的定义是:应该有且只有一个原因引起类的改变,即一个类只负责一个职责。
比如让类C负责两个不同的职责:职责P1,P2。当由于职责P1需求发生改变而需要修改类C时,有可能会导致原本运行正常的职责P2功能发生故障。
(2)举例
关于用户管理的一个类按如下类图来设计:
很显然,用户的属性和行为没有分开,按照单一职责原则,应该将其重新拆封成两个接口:用户属性接口IUserBO,用户行为接口IUserBiz。
分清职责之后的代码如下:
...... IUserInfo userInfo = new UserInfo(); // 操作用户的属性 IUserBo userBo = (IUserBo)userInfo; userBo.setPassword("123"); // 操作用户的行为 IUserBiz userBiz = (IUserBiz)userInfo; userBiz.delete(userBo); ......
(3)总结
单一职责原则适用于接口、类、方法,一个方法尽可能地完成一件事情。比如一个修改密码的方法,就不要把它放在修改用户信息这个大的方法中去,否则方法的职责就不清晰了。但是过分细分类的职责又会人为的增加系统的复杂性,比如本来一个类可以实现的行为硬拆分为两个类,然后再使用聚合或者组合的方式耦合在一起,就人为的制造了麻烦。由于单一的职责这个“职责”没有一个量化的标准,所以最难划分的还是职责,这个还是要根据实际情况和个人经验来定。
2. 里氏替换原则(LSP)
(1)概念
里氏替换原则针对的对象是具有继承关系的子类和父类。
里氏替换原则的定义是:只要父类出现的地方子类就可以出现,而且将其替换为子类也不会产生任何出错或者异常。
子类必须完全实现父类的方法(方法不能为空)。即父类的方法必须是子类全部需要的,如果不是全部需要的,就违背了LSP原则。
在类中调用其他类时必须使用父类或者接口,如果不使用父类或者接口,则类的设计违背了LSP原则。
(2)举例
某公司有普通用户和vip用户,他们发邮件的过程如下:
分析发现普通用户和vip用户发邮件的过程是相同的,即两个send()方法重复。将来还可能增加用户新类型,为了让系统具有更好的扩展性,使用里氏替换原则进行重构:
(3)总结
里氏替换原则包含以下4层含义:
子类必须完全实现父类的方法(方法体不为空);
子类可以有自己特有的方法;
重写父类的方法时输入参数可以被放大(子类中重写父类方法的前置条件必须与父类中被重写方法的前置条件相同或者更宽松);
重写父类的方法时输出结果可以被缩小(子类中重写父类方法的返回值必须小于等于父类中被重写方法的返回值)。
当然如果在编程过程中违反了LSP原则在运行时也不会出现什么问题,但是会遗留下很多潜在的问题会给以后的变更、维护工作造成很大的困难。
3. 依赖倒置原则(DIP)
(1)概念
依赖倒置原则的定义是:实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。即面向接口编程。
实现类依赖接口或者抽象类,而接口或者抽象类不依赖于实现类。
(2)举例
司机开奔驰车的类图如下:
实现代码如下:
// 司机的实现类 public class Driver { public void drive(Benz benz) { benz.run(); } }
// 奔驰车的实现类 public class Benz { public void run() { System.out.println("奔驰车出发..."); } }
// 场景调用类 public class Scene { public static void main(String[] args) { Driver driver = new Driver(); Benz benz = new Benz(); driver.drive(benz); } }
看起来好像这样设计是没有任何问题的,但我们常说“危难时刻见真情“,在技术上即“变更才显真功夫”。现在司机不仅要开奔驰,要开宝马车。但司机driver只有开奔驰的方法,而没有开动宝马车的方法啊,这就不合理了。这里只是增加了一个车类就要修改司机类,这是不稳定的、易变的。引用依赖倒置原则,采用面向接口编程的思想设计类图如下:
实现代码如下:
// 司机接口类 public interface IDriver { public void drive(ICar car); }
// 汽车接口类 public interface ICar { public void run(); }
// 司机的实现类 public class Driver implements IDriver { @Override public void drive(ICar car) { car.run(); } }
// 奔驰车的实现类 public class Benz implements ICar { @Override public void run() { System.out.println("奔驰车出发..."); } }
// 宝马车的实现类 public class BMW implements ICar { @Override public void run() { System.out.println("宝马车出发..."); } }
(3)总结
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。
每个类应该尽量都有接口或者抽象类,或者两者都有。
变量的表面类型尽量是接口或者抽象类。
依赖倒置原则使类之间不存在依赖关系,可以进行独立的并行开发,而且两个类的单元测试也可以独立地运行。
6大设计原则详解(二):http://www.cnblogs.com/LangZXG/p/6242927.html
6大设计原则,与常见设计模式(概述):http://www.cnblogs.com/LangZXG/p/6204142.html