一、开闭原则
一个软件实体应当对扩展开放,对修改关闭。
原文:Software entities should be open for extension, but closed for modification.
引自:Bertrand Meyer[MEYER88]
这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
换言之,应当可以在不必修改源代码的情况下改变这个模块的行为。
用面向对象的语言来讲,不允许修改的是系统的抽象层,而允许扩展的是系统的实现层。
二、里氏代换原则(LSP)
如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P
在所有的对象 o1 都被代换成 o2 时,程序 P 的行为没有变化,那么类型 T2 是类型 T1 的子类型。
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出
基类对象和子类对象的区别。
里氏代换原则是继承复用的基石。只有当衍生类可以替换掉基类,软件单位的功能不会收到影响时,
基类才能真正被复用,而衍生类也才能够在基类的基础上增加新的行为。
反过来的代换是不成立的。即如果一个软件实体使用的是一个子类的话,那么它不一定使用于基类。
比如,Base 是 Derived 的基类。有一个方法 method1 接受子类对象为参数:
method1(Derived d)
那么一般而已不可以有 method1(b)。 注:b 为 Base 类的实例
三、依赖倒转原则(Dependence Inversion)
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。
传统的过程性系统的设计办法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次。
依赖倒转原则是要把这个错的依赖关系倒转过来,这就是“依赖倒转原则”的来由。
通常,依赖(耦合)关系分为三种:
1. 零耦合(Nil Coupling)关系:两个类之间没有耦合关系。
2. 具体耦合(Concrete Coupling)关系:具体性耦合发生在两个具体的(可实例化的)类直接,经由
一个类对另一个具体类的直接引用造成。
3. 抽象耦合(Abstract Coupling)关系:抽象耦合关系发生在一个具体类和一个抽象类(或者java接口)
之间,使两个必须发生关系的类之间存有最大的灵活性。
依赖倒转原则要求客户端依赖于抽象耦合。他的另一种表述是:要针对接口编程、不要针对实现编程。
通常,实现依赖倒转原则的手段是:工厂方法模式、模板方法模式、迭代子模式(这也是 Collection 类型
在可以通过 for 、while 方式遍历的情况下,还提供 iterator 遍历方法供调用方使用的原因)。
四、接口隔离原则(Interface Segregation)
接口隔离原则讲的是:使用多个专门的接口比使用单一的总接口要好。 换言之,从一个客户类的角度来讲:
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
由于每一个接口都代表一个角色,实现一个接口的对象,在他的整个生命周期中都扮演这个角色。因此将
角色划分清楚是系统设计的一个重要工作。因此,一个符合逻辑的推断,不应当
将几个不同的角色都交给同一个接口,而应当交给不同的接口。
过于臃肿的接口是对接口的污染(Interface Contamination)。
五、合成/聚合复用原则(Composite/Aggregate Reuse)
合成/聚合复用原则(CARP)经常又叫合成复用原则(CRP)。合成/聚合复用就是在一个新的对象里面使用一些
已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。这个设计
原则有另一个更简短的表述:要尽量使用合成/聚合,尽量不要使用继承。
Java API 中,有几个明显违反这一原则的例子,其中最为著名的就是 Stack 和 Properties。前者被不当地设置
为 Vector 的子类,而 Properties 被不恰当地设置成 Hashtable 的子类。
一个 Stack 不是一个 Vector(NOT IS-A),所以 Stack 不应当设置成为 Vector 的子类。同样地,一个 Properties
也不是一个 Hashtable。 在这两种情况下,使用聚合比使用继承关系更合适。
由于 Properties 继承了 Hashtable 的行为,因而当 p 是一个 Properties 类型的对象是,p.getProperties(key)
与 p.get(key) 就会给出不同的结果。前者来自于 Properties 本身,因此会利用默认值;而后者则来自于 Hashtable,
因此不会使用默认值。
更糟糕的是,由于 Properties 是 Hashtable 的子类,因此,客户端可以通过类型的转换,直接使用超类的行为。
比如,Properties 假定所有的键和值都是 String 类型的,如果不是,就会导致运行崩溃。但是,客户端完全可以
通过 Hashtable 提供的行为加入任意类型的键和值,绕过 Properties 的接口,并导致 Properties 的内部矛盾和
崩溃。
这样说来,Properties 其实仅仅是有一些 Hashtable 的属性的,换言之,这是一个 "Has-A" 的关系,而不是一个
"Is-A" 的关系。
六、迪米特法则(LoD)
迪米特法则(Law of Demeter)又叫做最少知识原则(Least Knowledge Principle),就是说,一个对象应当对其他
对象有尽可能少的了解。
迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,这条法则实际上是很多著名系统,比如火星登录软件
系统、木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则。
关于迪米特法则,比较有代表性的几种描述如下:
1. 只与你直接的朋友们通信(Only talk to your immediate friends)
2. 不要跟“陌生人”说话(Don't talk to strangers)
3. 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
迪米特法则的主要用意是控制信息的过载。在将迪米特法则运用到系统设计中时,要注意下面的几点:
1. 在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。一个处在弱耦合中的类一旦被修改,
不会对有关系的类造成波及。
2. 在类的结构设计上,每一个类都应当尽量降低成员的访问权限(Accessibility)。换言之,一个类包装好各自的
private 状态(自己的“秘密”)。 这样一来,想要了解其中的一个类的意义时,不需要了解很多别的类的细节。一个
类不应当 public 自己的属性,而应当提供取值和赋值方法让外界间接访问自己的属性。
3. 在类的设计上,只要有可能,一个类应当设计成不变类。
4. 在对其他类的引用上,一个对象对其对象的引用应当降到最低。