设计模式系列开篇:六大原则

设计模式说白了就是一套由前人们总结出来的一套可以反复使用的经验理论。它可以提高代码的可重用性,增强系统的可维护性,加强代码的可读性。

在这套经验理论里,前人们提出了以下六个原则,需要我们在设计时来遵循。

单一职责原则

Single Responsibility Principle (SRP):就一个类而言,应该仅有一个引起它变化的原因。

一个类应该是一组相关性很高的函数、数据的封装。两个完全不一样的功能就不应该放在一个类中。

实际上,如何去划分一个类、一个函数的职责,每个人都有自己的看法。这需要根据个人经验和具体的业务逻辑而定。所以这个原则从概念上来看最简单,实施起来却是最难。

所幸的是,它也有一些基本的指导原则,比如:一个类应该是一组相关性很高的函数、数据的封装。两个完全不一样的功能就不应该放在一个类中。

单一职责原则具有以下优点:

  1. 类的复杂度降低,提高了可读性。
  2. 降低变更引起的风险,容易维护。

里氏替换原则

Liskov Substitution Principle (LSP):所有引用父类的地方必须能透明地使用其子类的对象。

这里还存在另外一个定义,相对拗口一点,需要多理解几遍。如果对每一个类型为S的对象O1,都存在有类型为T的对象O2,使得在以T定义的所有程序P里,所有的对象O2都可以用O1替换,程序P的行为没有发生变化,那么类型S为类型T的子类。

简单来说就是,只要父类能出现的地方,子类就可以出现。而且替换为子类也不会产生任何错误或者异常。但是反过来就不行了,有子类出现的地方,父类未必就能适应。

它的优点是:

  1. 提高代码的重用性,子类拥有父类的方法和属性。
  2. 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性。

它的缺点是:

  1. 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性。
  2. 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成非常糟糕的结果,要重构大量的代码。

依赖倒转原则

定义

Dependency Inversion Principle (DIP):高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖于细节;细节应当依赖于抽象。

在Java中,细节就是指接口或抽象类,细节就是指实现类。所以这个定义在Java中的表现就是:

  1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
  2. 接口或抽象类不依赖于实现类。
  3. 实现类依赖接口或抽象类。

更简单的定义就是针对接口编程,而不是针对实现编程。

依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

运用

我们用一个司机开汽车的例子来深入了解下依赖倒转原则。

首先有一个奔驰车类。

1
2
3
4
5
public class Benz {
public void run (){
Log.d("Chen", "奔驰车在飞奔...");
}
}

接着有一个司机类,司机可以驾驶奔驰车。

1
2
3
4
5
public class Driver {
public void drive (Benz benz) {
benz.run();
}
}

这样,我们就可以在测试方法中,完成司机开奔驰车了。

1
2
3
Driver xiaoWang = new Driver();
Benz benz = new Benz();
xiaoWang.driver(benz);

这个程序看起来很完美,直到有一天,出现了一个宝马汽车类。

1
2
3
4
5
public class Bmw {
public void run (){
Log.d("Chen", "宝马车在飞奔...");
}
}

尴尬的事来了,小王作为一个可以开奔驰的司机,居然没办法驾驶宝马车。这显然是不合理的。这就是实现类之间发生了直接的依赖关系带来的,它会加重类间的耦合性,降低代码的可维护性。

所以我们这里需要设计出司机和汽车的接口,通过它们产生依赖关系。

1
2
3
4
5
6
7
public interface Icar {
public void run();
}

public interface IDriver {
public void driver(Icar car);
}

接着司机类实现司机接口,奔驰、宝马实现汽车接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Driver implements IDriver {
public void driver(Icar car) {
car.run();
}
}

public class Benz implements ICar{
public void run (){
Log.d("Chen", "奔驰车在飞奔...");
}
}

public class Bmw implements ICar{
public void run (){
Log.d("Chen", "宝马车在飞奔...");
}
}

现在不管奔驰、宝马,还是其他的汽车,小王都可以驾驶了。

1
2
3
4
5
IDriver xiaoWang = new Driver();
Icar benz = new Benz();
Icar bmw = new Bmw();
xiaoWang.driver(benz);
xiaoWang.driver(bmw);

接口隔离原则

Interface Segregation Principle (ISP):客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。

我们需要将非常庞大臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。

也就是说,要让客户端依赖的接口尽可能少

迪米特法则

Law of Demeter (LoD):也称为最少知识原则,一个对象应该对其他对象有最少的了解。

通俗地讲就是,一个类应该对自己需要耦合或调用的类知道的最少。类的内部如何实现与调用者或者依赖者没关系,调用者或者依赖者只需要知道它需要的方法即可,其他的可一概不用管。

LoD要求我们只与直接的朋友通信,不跟陌生人说话。朋友类的定义是,出现在成员变量、方法的输入输出参数中的类。出现在方法体内部的类不属于朋友类。

LoD的核心观念就是类间解耦,只有弱耦合了以后,类的复用率才可以提高。不过它也会带来系统的复杂性的提高,为维护带来难度。所以采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。

开闭原则

Open Closed Principle (OCP):软件中的对象(类、模块、函数等)应当对扩展是开放的,但对修改是封闭的。

程序一旦开发完成,程序中的一个类只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类实现。新建的类可以通过继承的方式重用原类的代码。

开闭原则是一个终级目标,任何人也无法百分之百做到。但以它为指引,朝着这个方向努力,可以非常显著地改变一个系统的架构,真正做到拥抱变化。

Chen wechat
欢迎扫描二维码,订阅我的博客公众号MiracleChen