1、开闭原则的定义
软件实体(类,模块,函数等等)应当对扩展开放,对修改关闭。 — 勃兰特·梅耶
定义中的软件实体可以包括几个部分:项目中划分出的模块、类、接口与方法。
开闭原则主张:当一个业务当需求发生变动时,我们期望代码可以通过扩展模块的方式去满足新的需求,而不是去修改原来的代码。这是对软件实体的灵活性、稳定性的一种考验。
但是现实开发中,我们很难,或者说一次性很难完成这一项工作,需求的变动往往也“不按逻辑出牌”,所以开闭原则的目的并不是定义某种设计原则,而是对所有设计原则对一种规范。换言之,开闭原则是所有设计原则的根本,也是面向对象语言设计的终极目标。
2、开闭原则的做用
- 避免重复的软件测试
开闭原则规范指出,期望代码可以通过扩展模块的方式去满足新的需求。如我们通过继承的方式重新定义了一个类去满足新的需求,则我们只需要测试子类即可,而不用考虑父类的测试。 - 提高代码的可复用性
代码功能被切割的越细(即粒度越小),被复用的可能性就越大。当然这并不意味着粒度越小越好。粒度的把控需要基于我们平时开发过程中大量的经验积累。 - 提高软件的可维护性
遵守开闭原则可以使得软件稳定性高、延续性强,从而易于扩展和维护。
以一个工厂为例,该共产需要一条流水线A去生产产品A,如果我们将产品A的材料获取、材料拼装等功能一股脑的压缩在一条流水线上,当流水线建成时,我们确实可以很“巴适”地去获取最终的产品,之后我们甚至可以不用考虑该产品是如何生产出来的。然而当该工厂同时需要生产产品B,产品B与产品A公用了相同的材料(如玻璃),那么在流水线B中,我们不得不实现一个与A一摸一样的玻璃材料获取方式。这样显然是不合理的。这时候,我们更期望将生产玻璃的这个功能“抽象”出来,由一个单独的流水线C完成。对于A、B而言,你只管要,我负责给。在整个流水线实验阶段中,我们可以一个一个检测这些抽象出来的流水线有没有问题,而不是一出问题整个流水线都要重新检测一下。同时我们可以任意更换子流水线,去实现产品变动、升级等业务需求。
3、开闭原则的种类
开闭原则的命名被应用在两种方式上。这两种方式都使用了继承来解决明显的困境,但是它们的目的,技术以及结果是不同的。
3.1、梅耶开闭原则
梅耶的定义提倡实现继承。具体实现可以通过继承方式来重用,但是接口规格不必如此。已存在的实现对于修改是封闭的,但是新的实现不必实现原有的接口。
// 产品A V1版本
public class PipelineA_V_1 {
public Material getMaterial() {
return new Material(100);
}
public ProductA assembleProductA() {
Material material = getMaterial();
return new ProductA(material);
}
}
// 产品A V2版本
public class PipelineA_V_2 extends PipelineA_V_1 {
@Override
public Material getMaterial() {
return new Material(200);
}
}
3.2、多态开闭原则
多态开闭原则的定义倡导对抽象基类的继承。接口规约可以通过继承来重用,但是实现不必重用。已存在的接口对于修改是封闭的,并且新的实现必须至少实现那个接口。
// 抽象出获取材料的方法
public abstract class PipelineB {
public abstract Material getMaterial();
public ProductB assembleProductB() {
Material material = getMaterial();
return new ProductB(material);
}
}
// V1版本
public class PipelineB_V_1 extends PipelineB {
@Override
public Material getMaterial() {
return new Material(100);
}
}
// V2版本
public class PipelineB_V_2 extends PipelineB {
@Override
public Material getMaterial() {
return new Material(200);
}
}