开闭原则(Open-Close Principle,OCP):
定义:规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的。
这意味着应该用抽象定义结构,用具体实现扩展细节,以此确保软件系统开发和维护过程的可靠性。
对于外部的调用方来说,只要能体现出面向抽象编程,定义出接口并实现其方法,即不修改原有方法体,只通过继承方式进行扩展,都可以体现出开闭原则。
听起来很简单是不?
我们来更好的理解一下:一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
也就是说如果出现使用方需要修改原有代码来实现变化的情况,就等于违反了开闭原则。
通俗解释就是,添加一个新的功能,应该通过在已有代码(模块、类、方法)的基础上进行扩展来实现,而不是修改已有代码
那这就要求我们,对于业务系统,要想识别出尽可能多的扩展点,就要求你对业务有足够的了解,能够预见一些未来可能的变化。
对于偏技术的系统,比如,框架、组件、类库等,就需要充分了解它的使用场景?以及今后想要扩展点功能?使用者(业务程序员)未来会有哪些更多的诉求.
举例:
用计算三种形状的面积的例子,长方形、三角形、圆形,它们在类中已经按照固定的公式实现,圆形面积公式中π=3.14。但后续由于π值取的精度对于某些场景是不足的,需要扩展,接下来就通过模拟这个场景体现开闭原则。
public interface ICalculationArea {
/**长方形面积计算*/
double rectangle(double x, double y);
/**三角形面积计算*/
double triangle(double x, double y, double z);
/**圆形面积计算*/
double circular(double r);
}
长方形面积,长×宽。
三角形面积,使用海伦公式,S=
,其中p=(a+b+c)/2。
圆形面积:
实现类:
public class CalculationArea implements ICalculationArea{
private final static double π = 3.14D;
@Override public double rectangle(double x, double y) {
return x * y;
}
@Override public double triangle(double x, double y, double z) {
double p = (x + y + z) / 2;
return Math.sqrt(p * (p - x) *(p - y) * (p - z));
}
@Override public double circular(double r) {
return π * r * r;
}
}
其中圆形面积的 π 值取的是 3.14D. 现在我们需要扩展circular方法,让其支持精度更高的场景
违背原则方案, 直接将CalculationArea中 π 的值改掉:
private final static double π = 3.141592653D;
@Override public double circular(double r) {
return π * r * r;
}
开闭原则方案:
继承父类扩展需要的方法,同时可以保留原有的方法,新增自己需要的方法。
public class CalculationAreaExt extends CalculationArea{
private final static double π = 3.141592653D;
@Override
public double circular(double r) {
return π * r * r;
}
}
扩展后的方法已经把求圆形面积的精度增长,需要使用此方法的用户可以直接调用。而其他的方法,如长方形面积、三角形面积,则可以继续使用。
开闭原则中对于修改是封闭的并非是一个绝对的概念。
1.修复缺陷所做的改动
缺陷在软件中很常见,是不可能完全消除的。当缺陷出现时,就需要我们修复现有的代码。软件修复明显倾向于实用主义而不是坚持开放封闭原则。
2.客户端无法感知到的改动
如果一个类的改动会引起另一个类的改动,那么这两个类就是紧密耦合的。相反,如果一个类的修改总是独立的,并不会引起其他类的改动,那么这些类就是松散耦合的。我们要记住,任何情况下,松散耦合都比紧密耦合要好。如果我们对现有代码的修改不会影响客户端代码,那么也就谈不上违背开放封闭原则。
3.修改还是扩展?
从开闭原则定义中,我们可以看出,开闭原则可以应用在不同粒度的代码中,可以是模块,也可以类,还可以是方法(及其属性)。同样一个代码改动,在粗代码粒度下,可以被认定为“修改”,但在细代码粒度下,又可以被认定为“扩展”。
比如,在类这个层面添加属性和方法相当于修改类,这个代码改动可以被认定为“修改”;但这个改动并没有修改已有的属性和方法,在方法(及其属性)这一层面,它又可以被认定为“扩展”。