设计原则的目的
- 确保可维护性。软件能够被理解、修改、扩展。
- 确保可复用性。实现设计方案或者源代码的复用。
单一职责原则
一个对象应该只包含单一职责,并且该职责被完整的封装在一个类中。
- 单一职责原则(Single Responsibility Principle, SRP),是最简单也是最难运用的设计原则,用于控制类的粒度。是高内聚、低耦合的知道方针。
- 一个类的职责越多,被复用的可能性越小。当其中一个职责变化时,可能会影响到其他职责的运作。因此需要职责分离。
实例
- 客户信息图形统计模块,将客户信息通过图形展示。
- 初始版本:获得数据库连接、查询用户信息、创建和显示图表均放在一个类中,无论是修改图表显示还是数据库连接方式,都需要修改这个类。违反单一职责原则。
- 重构版本:拆分为3个类。
- DBUtil负责连接数据库。
- CustomerDao负责操作数据库的表,获取用户信息。
- CustomerDataChart负责图表相关,包括显示和生成。
开闭原则
软件实体应该对扩展开放,对修改关闭。
- 开闭原则(Open-Closed Principle, OCP),是最重要的设计原则,要求尽量在不修改原有代码的情况下进行扩展,关键是抽象化。
里氏替换原则
所有引用基类的地方必须能透明地使用其子类。
- 里氏代换原则(Liskov Substitution Principle, LSP),是实现开闭原则的重要方式之一,要求将基类对象替换为子类对象时,程序没有错误或异常。
- 基类可以设计为抽象类、接口,增加新的功能通过增加新的子类实现,无需修改原有代码。
依赖倒转原则
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 依赖倒转原则(Dependency Inversion Principle, DIP),是面向对象设计的主要实现机制之一,要求针对接口编程。只需要扩展抽象层,通过配置文件指定实现类。
关系
- 开闭原则是目标,里氏替换原则是基础,依赖倒转原则是手段,相互补充,角度不同。
实例
- 将存储在txt或者excel等文件中的客户数据存储到数据库中。
- 初始版本:通过局部变量调用数据转换类,因为数据来源不一致,需要经常修改CustoerDao代码,更换数据转换类,违反开闭原则。
- 重构版本:针对数据转化抽象类编程,通过配置文件指定实现类。
- 初始版本:通过局部变量调用数据转换类,因为数据来源不一致,需要经常修改CustoerDao代码,更换数据转换类,违反开闭原则。
接口隔离原则
客户端不应该依赖那些它不需要的接口。
- 接口隔离原则(Interface Segregation Principle, ISP),要求每一个接口承担一种相对独立的角色,使接口的职责相对单一。
- 需要注意接口也不能划分太小,否则会接口泛滥,通常接口中仅包含某一类用户定制的方法。
实例
- 客户数据显示模块。
- 初始版本:接口中包含读取文件、转化为xml格式、创建和展示图表、创建和展示文字报表,职责多样,很多类只需要实现其中的部分功能,但是仍然需要实现不相关的接口。不符合接口隔离原则。
- 重构版本:根据数据源格式和生成报告还是图表确定4个接口,每个接口角色较为单一。
- 初始版本:接口中包含读取文件、转化为xml格式、创建和展示图表、创建和展示文字报表,职责多样,很多类只需要实现其中的部分功能,但是仍然需要实现不相关的接口。不符合接口隔离原则。
合成复用原则
优先使用对象组合,而不是继承来达到复用的目的。
- 合成复用原则(Composite Reuse Principle, CRP),要求优先使用关联(包括组合聚合)来复用,这样可以降低类之间的耦合度,使系统更加灵活。
- 通过继承的复用会将基类的实现暴露给子类,破坏封装性,称为白箱复用;通过关联复用则成员变量的内部实现不可见,称为黑箱复用。
实例
- 访问数据库的客户信息。
- 初始版本:dao类需要使用数据库工具类,通过继承来复用。如果更换数据库,无论是修改dao类为继承另一个工具类,还是直接修改工具类,都将违反开闭原则。
- 重构版本:将工具类的父类作为dao类的成员变量使用,dao类针对父类编程。不同数据库的工具类可以继承父类工具类,从而实现功能的扩展。
- 初始版本:dao类需要使用数据库工具类,通过继承来复用。如果更换数据库,无论是修改dao类为继承另一个工具类,还是直接修改工具类,都将违反开闭原则。
迪米特法则
每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
- 迪米特法则(Law of Demeter, LoD),要求尽可能少与其他实体发生相互作用,这样可以降低系统之间的耦合度。
- 一个对象应该只和自己的朋友相互作用,包括:
实例
- 操作窗口按钮和响应。
- 初始版本:一个按钮被单击时,列表框、组合框、文本框、文本标签都会改变。这些控件关系复杂,在窗口中新增控件时,需要修改和它交互的所有控件的源代码,扩展性很差。
- 重构版本:使用控件交互的中间类,降低控件的耦合度,新增控件只需要修改中间类即可。
- 初始版本:一个按钮被单击时,列表框、组合框、文本框、文本标签都会改变。这些控件关系复杂,在窗口中新增控件时,需要修改和它交互的所有控件的源代码,扩展性很差。