总体来说设计模式分为三大类:
- 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
六大基本原则
单一职责原则
单一职责原则(Single Responsibility Principle,简称SRP)
定义:应该有且仅有一个原因引起类的变更。
单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责。
优点:
- 类的复杂性降低,实现什么职责都有明确的定义。
- 可读性提高,复杂度降低。
- 可维护性提高。
- 变更引起的风险降低,
单一职责使用于接口、类,同时也适用于方法,一个方法尽可能做一件事情。
对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂度。
类的单一职责确实受非常多因素的制约,纯理论地来讲,这个原则是非常优秀的,但是现实有实现的难处,你必须考虑项目的工期、成本等因素。
对于单一执着原则,建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
里氏替换原则
里氏替换原则(Liskov substitution Principle,LSP)
定义:所有引用基类的地方必须能透明地使用其子类的对象。
只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必能适应。
优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。
- 提高代码的可用性。
- 子类可以形似父类,但又异于父类。
- 提高代码的扩展性。
缺点:
- 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法。
- 让子类多了父类的约束,降低了代码的灵活性。
- 增加了耦合性。
里氏替换原则为良好的继承定义了规范:
1、 子类必须完全实现父类的方法:
定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这已经使用了里氏替换原则。
在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计违背了LSP原则。
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生了畸变,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承
2、子类可以有自己的个性:
子类可以有自己的行为和外观,也就是方法和属性。因为里氏替换原则可以正着用,但是不能反过来用,在子类出现的地方,父类就未必可以胜任。
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP)
定义:高层模版不应该依赖底层的模块,两者都应依赖其抽象;
抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则在Java语言中的表现是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖实现类;
- 实现类依赖接口或抽象类。
依赖的三种写法
依赖是可以传递的,A对象依赖B对象,B又依赖C,C又依赖D......记住一点:只要做到抽象依赖,即使多层的依赖传递也不用畏惧。
1、构造函数传递依赖对象
在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入。
public interface IDriver{
public void drive();
}
public class Driver implements IDriver{
private Icar car;
public Driver(ICar car){
this.car = car;
}
public void drive(){
this.car.run();
}
}
2、Setter方法传递依赖对象
在抽象中设置setter方法声明依赖关系。
public interface IDriver{
public void setCar(Icar car);
public void drive();
}
public class Driver implements IDriver{
private Icar car;
public setCar(ICar car){
this.car = car;
}
public void drive(){
this.car.run();
}
}
3、接口声明依赖对象
在接口的方法中声明依赖对象。
public interface IDriver{
public void drive(ICar car);
}
public class Driver implements IDriver{
public void drive(ICar car){
car.run();
}
}
依赖倒置原则的本质是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。
在项目中只要遵循以下几个规则就可以了:
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备。
- 变量的表面类型尽量是接口或者抽象类。
- 任何类不应该从具体类派生
接口隔离原则
定义:客户端不应该依赖不需要的接口。类间的依赖关系应该建立在最小的接口上。
建立单一接口,不要建立臃肿庞大的接口。接口尽量细化,同时接口中的方法尽量少。可能会有疑问,这于单一职责原则不是相同吗?接口隔离原则于单一职责原则的审视角度是不相同的,单一职责原则要求的类和接口的职责单一,重视的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。例如,一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,按照单一职责原则是允许的按照接口隔离原则是不允许的。提供给每个模块的都应该是安逸接口,提供给几个模块就应该有几个接口。
接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装:
- 一个接口只服务于一个子模块或业务逻辑;
- 通过业务压缩接口中的方法;
- 已经被污染了的接口,尽量去修改,若变更风险较大,则采用适配器模式进行转化处理了;
迪米特法则
定义:也称最少知识原则,一个对象应该对其他对象有最少的了解,通俗的讲一个类应该对自己需要耦合和调用的类知道得最少。
一个类尽量不要通陌生类交流,不要出现getA().getB().getC()这中情况。类于类之间的关系是建立在类间,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象
一个类公开的public属性或方法越多,修改时涉及的面也越大,变更引起的风险扩散也越大。因此在设计时需要反复衡量:是否还可以减少public方法和属性,是否可以修改为private、protected等访问权限,是否可以加上final
在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也可以,那应该怎么去衡量: 如果一个方法放在本类中,既不增加类间的关系,也对本类不产生负面影响,那就放在本类中
迪米特法则的核心观念就是类间解藕,若耦合。只有若耦合后类的复用率才可以提高。
任何两个素不相识的人中间最多隔着6个人,即只通过6个人就可以讲他们联系在一起,这是著名的六度分隔理论。但在实际的开发中,如果一个类跳转两次以上才能访问到另一类,就需要想方法进行重构了,跳转次数越多,系统越复杂,维护越困难。
开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则对扩展开发,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个鼓励无意义的代码片段:
- 逻辑变化:只变化一个逻辑,而不设计其他模块,可以通过修改原有类中的方法的方式来完成,前提是所有依赖或关联类都按照相同的逻辑处理
- 子模块变化:在通过扩展完成变化时,高层次的模块修改时必然的,总要有地方去调用你的扩展
开闭原则是一个非常虚的原则,前面5个原则都是对开闭原则的具体解释。
下一篇:设计模式:(二)创建型模式