二、依赖倒转原则
1.依赖倒转原则定义
i. 高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
ii. 要针对接口编程,不要针对实现编程。
2. 依赖倒转原则分析
a) 简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。
b) 实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
c) 依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中。
d) 类之间的耦合 1.零耦合关系 2.具体耦合关系 3.抽象耦合关系。 依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键。
e) 依赖注入:
构造注入:通过构造函数注入实例变量
设值注入:通过Setter方法注入实例变量
接口注入:通过接口方法注入实例变量
3. 依赖倒转原则实例
某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如可以转换来自数据库的数据(DatabaseSource)、也可以转换来自文本文件的数据(TextSource),转换后的格式可以是XML文件(XMLTransformer)、也可以是XLS文件(XLSTransformer
图(一)
图(二)
图(一)和图(二)分析:
图(一)为什么要到图(二)哪?因为该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类MainClass都需要修改源代码,以便使用新的类,但违背了开闭原则。现使用依赖倒转原则对其进行重构。
总结:高层模块不应该依赖底层模块,两个都应该依赖与抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
三、 里氏代换原则
1. 定义
i. 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
ii. 所有引用基类(父类)的地方必须能透明地使用其子类的对象。
2.分析
i. 在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
ii. 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
3. 实例
某系统需要实现对重要数据(如用户密码)的加密处理,在数据操作类(DataOperator)中需要调用加密类中定义的加密算法,系统提供了两个不同的加密类,CipherA和CipherB,它们实现不同的加密方法,在DataOperator中可以选择其中的一个实现加密操作。如图所示:
图(一)
图(二)
图(一)和图(二)分析:
图(一)为什到图(二)哪?因为如果需要更换一个加密算法类或者增加并使用一个新的加密算法类,如将CipherA改为CipherB,则需要修改客户类Client和数据操作类DataOperator的源代码,违背了开闭原则。现使用里氏代换原则对其进行重构,使得系统可以灵活扩展,符合开闭原则。
总结:子类型必须能够替换掉它们的父类型。
四、单一职责原则
1. 定义
i. 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
ii. 就一个类而言,应该仅有一个引起它变化的原因。
2. 分析
i. 一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
ii.类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
iii.单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
3. 实例
i. 某基于Java的C/S系统的“登录功能”通过如下登录类(Login)实现:
图(一)
图(二)
图一和图二有什么区别哪?
图(一)功能太过于集成,严重违反类的单一原则。
总结:就一个类而言,应该仅有一个引起它变化的原因。