前言
开闭原则(Open_Close Principle,OCP)是指一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。
强调的是用抽象对象构建框架,用实现扩展细节
开闭原则,是面向对象设计中,最基础的设计原则。它知道我们如何建立稳定灵活的系统。
例如:我们版本更新,尽可能不修改源代码,但是可增加新功能。
一、优点
- 1.保证软件产品的稳定性
开闭原则要求我们保持原有代码不变,添加新代码来实现软件的变化,因为不涉及源代码的改动,这样可以避免为实现新功能而影响线上功能的情况,避免系统不兼容的问题 - 2.不影响原有测试代码的运行
软件开发规范性好的团队都会写单元测试,如果原有的某个功能发生了变化,则单元测试代码也应做响应的变更,否则就有可能导致测试出错。如果每次软件的变化,除了变更功能代码之外,还得变更测试代码,书写测试代码同样需要消耗工时,这样在项目中引入单元测试就变成了累赘。开闭原则可以让单元测试充分发挥作用而又不会成为后期软件开发的累赘。 - 3.使代码更具模块化,易于维护
开闭原则可以让代码中的各功能,以及新旧功能独立存在与不同单元模块中,一但某个功能出现问,可以很快锁定代码位置做出修改,由于模块间代码独立不相互调用,更改一个功能的代码也不会引起其他功能的崩溃。 - 4.提高开发效率
在项目开发功能中,有时候阅读前人的代码是一件很头疼的事情,尤其是项目开发周期比较长,可能三五年,再加上公司人员流动性大,原有代码的开发人员早已另谋高就,所留下来的代码更是一团糟,自带混淆,能走弯路不走正路。而现在需要在原有功能的基础上添加新功能,如果开闭原则使用得当的情况下,则不需要看懂原有代码实现的细节便可以添加新代码实现新功能,毕竟有时候阅读一个功能代码,比自己重新实现用的时间还要长。
二、示例与实现
假如我们使用出售电脑为例,首先定义一个顶层接口Computer:
/**
* @Description: 顶层接口,定义了获取电脑信息的接口方法
* @aouthor Geoffrey·Archie
* @create 2020-09-25 14:40
*/
public interface Computer {
double getPrice(); // 获取价格
String getColor(); // 获取颜色
int getMemory(); // 获取内存
float getSize(); // 尺寸
}
然后定义两个实现类,华硕电脑与苹果Mac
/**
* @Description: 华硕
* @aouthor Geoffrey·Archie
* @create 2020-09-25 14:40
*/
public class AsusComputer implements Computer {
private double price;
private String color;
private int memory;
private float size;
// 有参构造参数
public AsusComputer(double price,String color,int memory,float size){
this.price = price;
this.color = color;
this.memory = memory;
this.size = size;
}
@Override
public double getPrice(){
return this.price;
}
@Override
public double getColor(){
return this.color;
}
@Override
public double getMemory(){
return this.memory;
}
@Override
public double getSize(){
return this.size;
}
}
/**
* @Description: Mac
* @aouthor Geoffrey·Archie
* @create 2020-09-25 14:40
*/
public class MacComputer implements Computer {
private double price;
private String color;
private int memory;
private float size;
// 有参构造参数
public MacComputer(double price,String color,int memory,float size){
this.price = price;
this.color = color;
this.memory = memory;
this.size = size;
}
@Override
public double getPrice(){
return this.price;
}
@Override
public double getColor(){
return this.color;
}
@Override
public double getMemory(){
return this.memory;
}
@Override
public double getSize(){
return this.size;
}
}
测试类
public class Test {
public static void main(String[] args){
Computer computer = new AsusComputer(4888.88D,"深空灰",8,14.0F);
System.out.println(
"电脑:华硕\n" +
"售价: " + computer.getPrice() + "\n" +
"颜色: " + computer.getColor() + "\n" +
"内存: " + computer.getMemory() + "\n" +
"尺寸: " + computer.getSize() + "\n"
);
}
}
电脑:华硕
售价:4888.88
颜色:深蓝
内存:8
尺寸:14.0
这是我们一开始的需求,但是随着软甲发布运行,我们需求不可能是一成不变,肯定要接轨市场。假设现在是双十一,需要搞促销活动。那么我们代码肯定要添加新的功能。可能有些人会在原有的代码上做变动:
@Override
public double getPrice() {
return this.price * 0.6;
}
这是不符合开闭原则的,虽然看起来这样做最直接,也最简单,但是绝大部分项目中,一个功能的实现远比想象要复杂的多,我们在原有的代码中进行修改,其风险远比扩展和实现一个方法要大的多。
正确的做法可以这样,遵循对扩展开发,对修改关闭:
/**
* @Description: 华硕电脑打折
* @aouthor Geoffrey·Archie
* @create 2020-09-25 14:40
*/
public class AsusDiscountComputer extends AsusComputer {
private float discount;
public AsusDiscountComputer(double price,String color,int memory,float size,float discount){
super(price,color,memory,size);
this.discount = discount;
}
public double getDiscountPrice() {
return getPrice() * this.discount;
}
}
实现一个关于折扣的子类,其中包含一个关于折扣的方法,这方法相当于一个扩展方法,可以看到这个子类是AsusComputer的,那为什么不把他设计成一个公用的折扣类呢,比如DisCountComputer,所有实现类都继承这个折扣类。这是因为每种实现类的折扣方案可能是不一样的。所以我们最好能把它作为每个实现类的子类单独实现。如果你能确保你的业务的功能能兼容所有相关联的需求你也可以公用同一个。
public class Test1 {
public static void main(String[] args){
Computer computer = new AsusDiscountComputer(4888.88D,"深空灰",8,14.0F,0.5F);
AsusDiscountComputer asusDiscountComputer = (AsusDiscountComputer)computer;
System.out.println(
"电脑:华硕\n" +
"原售价: " + asusDiscountComputer.getPrice() + "\n" +
"打折后售价: " + asusDiscountComputer.getDiscountPrice() + "\n" +
"颜色: " + asusDiscountComputer.getColor() + "\n" +
"内存: " + asusDiscountComputer.getMemory() + "\n" +
"尺寸: " + asusDiscountComputer.getSize() + "\n"
);
}
}
电脑:华硕
原售价:4888.88
打折后售价:2444.44
颜色:深蓝
内存:8
尺寸:14.0
你可以看到如果想要调用getDiscountPrice()方法,在原有的基础上你还要对他强转,如果你能确定新扩展的需求,能兼容原有的继承体系,也可以把它抽取到顶层的Computer接口中。
最后看一下继承体系
上面只是一个很简单的示例,在实际开发工程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、时间、成本、质量,不用刻意追求完美,要在适合的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构