1.单一职责原则(SRP):
一个类只能承担一个职责,即就一个类而言,应该仅有一个引起它变化的原因。因为每一个职责都是变化的一个轴线,当需求变化时,该变化会表现为类的职责的变化。如果一个类承担了多于一个的职责时,那么引起它变化的原因就有多个。
2.开-闭原则(OCP): 对扩展开放,对修改关闭
一个软件实体应该对扩展开放,对修改关闭。就是说,在设计一个模块的时候,应当使这个模块可以在不被修改的情况下被扩展。开-闭原则是面向对象设计可复用的第一块基石。
如何实现开-闭原则:
1)抽象化是关键
可以给出一个或多个抽象类或接口,规定出所有的具体类必须提供的方法的特征(signature)作为系统的抽象层。这个抽象层预见了所有的可能扩展。从抽象层导出的具体类可以修改或扩展系统的行为而不用修改抽象层。
2)对可变性封装原则
找到一个系统的可变因素把它封装成起来。
一个系统的可变性应当被封装成对象,同一种可变性的不同表象意味着同一个继承等级结构中的不同子类。不要把一种可变性和另外一种可变性混在一起,类的继承结构一般不要超过两层,不然就意味着把两种不同的可变性封装在了一起。
3.里氏替换原则(LSP): 子类型必须能够替换基类型
凡是使用基类型的地方,子类型一定适用,即子类可以替换基类。反之不成立。
里氏替换原则是继承复用的基石,只有子类可以替换掉基类,软件单位的功能不受影响时,基类才能真正的被复用。而子类才能在基类的基础上增加新的功能。
如:Base是基类,Sub是Base的子类,那么如果有方法method(Base b),则method(s)一定成立,其中s是Sub的对象。
LSP原则在设计模式中的体现:
策略模式:
合成模式:
代理模式:
4.依赖倒置原则(DIP:dependence inversion): 针对接口编程
抽象不应该依赖于具体,具体要依赖于抽象.(针对接口编程,不要针对具体编程。)
针对接口编程的意思是说,应该使用抽象类或接口进行变量的类型声明、参数的类型声明、方法的返回类型声明、以及数据类型的转换等。
不要针对具体编程的意思是说,不应该使用具体类进行变量的类型声明、参数的类型声明、方法的返回类型声明、以及数据类型的转换等.
如:不应该这样声明:
Vector employee = new Vector();
而应该这样声明employee:
List employee = new Vector();
这样的好处是决定将Vector改成ArrayList时,代码的改动比较少.
依赖倒置原则前提是假定所有的具体类都是变化的,有一些具体类可能是相当稳定,不会发生变化的。消费这个具体类的客户端就完全可以使用这个具体类,没必要为此发明一个抽象类型。
以抽象方式耦合是依赖倒置原则的关键。
5.接口隔离原则(ISP):
使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。向客户端提供public方法意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
定制服务:同一个角色提供宽、窄不同的接口,以对付不同的客户端。这样每一接口仅将客户需要的行为暴露给客户端,而没有将客户不需要的行为放在接口中。
适配器模式是接口隔离原则的一个应用。
6.合成/聚集复用原则(CARP:composite/aggregate reuse principle):
合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。即尽量使用合成/聚集复用而不是继承复用。
Has-A:代表一个类是另外一个类的一个组成部分或角色。
Is-A:代表一个类是另外一个类的一种。
如果两个类是“Has-A”关系那么应使用合成/聚集,如果是“Is-A”关系那么可使用继承。
7.迪米特法则(LoD:Law of Demeter):最少知识原则
又叫最少知识原则、不要和“陌生人”讲话原则,要求一个类(软件实体)应该尽可能少的和其它类(软件实体)发生作用。
不要向间接对象(陌生人)发送消息(讲话)。只应该向以下直接对象(朋友)发送消息:
1)this对象(自身).
2)方法的参数对象。
3)this的属性直接引用的对象。
4)作为this属性的集合中的元素对象。
5)在方法中创建的对象。
其意图是避免客户与间接对象和对象之间的对象连接产生耦合。
利用一个“中间人”是“迪米特法则”解决问题的办法。
与依赖倒置原则互补使用:
引入一个抽象类型”抽象陌生人”对象,使它成为某人的”朋友”。某人现在与抽象角色建立了朋友关系,这样的好处是“朋友”可以随时把“陌生人”换掉,只要“陌生人”实现了相同的抽象类型,那么某人就无法区分它们。如图
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
1)在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
2)在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。
3)在类的设计上,只要有可能,一个类应当设计成不变类。
4)在对其它对象的引用上,一个类对其它对象的引用应该降到最低。
5)限制局部变量的有效范围,有需要一个变量的时候才声明它,可以有效的限制局部变量的有效范围。
二.其它一些原则
1.关于抽象类
只要有可能,不要从具体类继承。
如图,在一个以继承关系形成的等级结构里面,树叶节点应该是具体类,而树枝节点应该是抽象类或接口。
抽象类应该拥有尽可能多的共同代码。
抽象类应该拥有尽可能少的数据。
在一个继承的等级结构中,共同的代码应该尽可能的往等级结构的上方移动。把重复的代码从子类移到超类中,可以提高代码的复用率。一个对象从超类继承过来的代码,在不使用时不会造成对资源的浪费。
一个对象的数据,无论是否使用都会占用资源,因此数据应该尽量放到具体类或等级结构的低端。
2.命令查询原则(command-query separation principle):
任何方法都可能是如下情况之一:
1)执行动作(更新,调整…..)的命令方法,这种方法通常具有改变对象状态等副作用,并且是void(没有返回值的)。
2) 向调用者返回数据的查询,这种方法没有副作用,不会永久性地改变任何对象的状态。
一个方法不能同时属于以上两种类型。
所以查询或者getter方法不会作任何修改,而命令也不会有任何返回值。
举个反面例子:
Missile m = new Missibe();
//看上去对我无妨!
String name = m.getName();
……….
public class Missile
{
…………….
public String getName()
{
launch();//发射导弹
return name;
}
…………….
}
一个类只能承担一个职责,即就一个类而言,应该仅有一个引起它变化的原因。因为每一个职责都是变化的一个轴线,当需求变化时,该变化会表现为类的职责的变化。如果一个类承担了多于一个的职责时,那么引起它变化的原因就有多个。
2.开-闭原则(OCP): 对扩展开放,对修改关闭
一个软件实体应该对扩展开放,对修改关闭。就是说,在设计一个模块的时候,应当使这个模块可以在不被修改的情况下被扩展。开-闭原则是面向对象设计可复用的第一块基石。
如何实现开-闭原则:
1)抽象化是关键
可以给出一个或多个抽象类或接口,规定出所有的具体类必须提供的方法的特征(signature)作为系统的抽象层。这个抽象层预见了所有的可能扩展。从抽象层导出的具体类可以修改或扩展系统的行为而不用修改抽象层。
2)对可变性封装原则
找到一个系统的可变因素把它封装成起来。
一个系统的可变性应当被封装成对象,同一种可变性的不同表象意味着同一个继承等级结构中的不同子类。不要把一种可变性和另外一种可变性混在一起,类的继承结构一般不要超过两层,不然就意味着把两种不同的可变性封装在了一起。
3.里氏替换原则(LSP): 子类型必须能够替换基类型
凡是使用基类型的地方,子类型一定适用,即子类可以替换基类。反之不成立。
里氏替换原则是继承复用的基石,只有子类可以替换掉基类,软件单位的功能不受影响时,基类才能真正的被复用。而子类才能在基类的基础上增加新的功能。
如:Base是基类,Sub是Base的子类,那么如果有方法method(Base b),则method(s)一定成立,其中s是Sub的对象。
LSP原则在设计模式中的体现:
策略模式:
合成模式:
代理模式:
4.依赖倒置原则(DIP:dependence inversion): 针对接口编程
抽象不应该依赖于具体,具体要依赖于抽象.(针对接口编程,不要针对具体编程。)
针对接口编程的意思是说,应该使用抽象类或接口进行变量的类型声明、参数的类型声明、方法的返回类型声明、以及数据类型的转换等。
不要针对具体编程的意思是说,不应该使用具体类进行变量的类型声明、参数的类型声明、方法的返回类型声明、以及数据类型的转换等.
如:不应该这样声明:
Vector employee = new Vector();
而应该这样声明employee:
List employee = new Vector();
这样的好处是决定将Vector改成ArrayList时,代码的改动比较少.
依赖倒置原则前提是假定所有的具体类都是变化的,有一些具体类可能是相当稳定,不会发生变化的。消费这个具体类的客户端就完全可以使用这个具体类,没必要为此发明一个抽象类型。
以抽象方式耦合是依赖倒置原则的关键。
5.接口隔离原则(ISP):
使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。向客户端提供public方法意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。
定制服务:同一个角色提供宽、窄不同的接口,以对付不同的客户端。这样每一接口仅将客户需要的行为暴露给客户端,而没有将客户不需要的行为放在接口中。
适配器模式是接口隔离原则的一个应用。
6.合成/聚集复用原则(CARP:composite/aggregate reuse principle):
合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。即尽量使用合成/聚集复用而不是继承复用。
Has-A:代表一个类是另外一个类的一个组成部分或角色。
Is-A:代表一个类是另外一个类的一种。
如果两个类是“Has-A”关系那么应使用合成/聚集,如果是“Is-A”关系那么可使用继承。
7.迪米特法则(LoD:Law of Demeter):最少知识原则
又叫最少知识原则、不要和“陌生人”讲话原则,要求一个类(软件实体)应该尽可能少的和其它类(软件实体)发生作用。
不要向间接对象(陌生人)发送消息(讲话)。只应该向以下直接对象(朋友)发送消息:
1)this对象(自身).
2)方法的参数对象。
3)this的属性直接引用的对象。
4)作为this属性的集合中的元素对象。
5)在方法中创建的对象。
其意图是避免客户与间接对象和对象之间的对象连接产生耦合。
利用一个“中间人”是“迪米特法则”解决问题的办法。
与依赖倒置原则互补使用:
引入一个抽象类型”抽象陌生人”对象,使它成为某人的”朋友”。某人现在与抽象角色建立了朋友关系,这样的好处是“朋友”可以随时把“陌生人”换掉,只要“陌生人”实现了相同的抽象类型,那么某人就无法区分它们。如图
迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:
1)在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。
2)在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。
3)在类的设计上,只要有可能,一个类应当设计成不变类。
4)在对其它对象的引用上,一个类对其它对象的引用应该降到最低。
5)限制局部变量的有效范围,有需要一个变量的时候才声明它,可以有效的限制局部变量的有效范围。
二.其它一些原则
1.关于抽象类
只要有可能,不要从具体类继承。
如图,在一个以继承关系形成的等级结构里面,树叶节点应该是具体类,而树枝节点应该是抽象类或接口。
抽象类应该拥有尽可能多的共同代码。
抽象类应该拥有尽可能少的数据。
在一个继承的等级结构中,共同的代码应该尽可能的往等级结构的上方移动。把重复的代码从子类移到超类中,可以提高代码的复用率。一个对象从超类继承过来的代码,在不使用时不会造成对资源的浪费。
一个对象的数据,无论是否使用都会占用资源,因此数据应该尽量放到具体类或等级结构的低端。
2.命令查询原则(command-query separation principle):
任何方法都可能是如下情况之一:
1)执行动作(更新,调整…..)的命令方法,这种方法通常具有改变对象状态等副作用,并且是void(没有返回值的)。
2) 向调用者返回数据的查询,这种方法没有副作用,不会永久性地改变任何对象的状态。
一个方法不能同时属于以上两种类型。
所以查询或者getter方法不会作任何修改,而命令也不会有任何返回值。
举个反面例子:
Missile m = new Missibe();
//看上去对我无妨!
String name = m.getName();
……….
public class Missile
{
…………….
public String getName()
{
launch();//发射导弹
return name;
}
…………….
}