## 一. 定义
开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该**对扩展开放,对修改关闭**。
**强调的是用抽象构建框架,用实现扩展细节。**
开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统。
例如:我们版本更新,尽可能不修改原代码,但是可以增加新功能。
## 二. 优点
##### **1.保持软件产品的稳定性**
开闭原则要求我们通过保持原有代码不变,添加新代码来实现软件的变化,因为不涉及原代码的改动,这样可以避免为实现新功能而改坏线上功能的情况,避免老用户的流失。
##### **2.不影响原有测试代码的运行**
软件开发规范性好的团队都会写单元测试,如果原有的某个功能发生了变化,则单元测试代码也应做相应的变更,否则就有可能导致测试出错。如果每次软件的变化,除了变更功能代码之外,还得变更测试代码,书写测试代码同样需要消耗工时,这样在项目中引入单元测试就成了累赘。开闭原则可以让单元测试充分发挥作用而又不会成为后期软件开发的累赘。
##### **3.使代码更具模块化,易于维护**
开闭原则可以让代码中的各功能,以及新旧功能独立存在于不同的单元模块中,一旦某个功能出现问题,可以很快地锁定代码位置作出修改,由于模块间代码独立不相互调用,更改一个功能的代码也不会引起其他功能的崩溃。
##### **4.提高开发效率**
在项目开发过程中,有时候阅读前人的代码是件很头疼的事,尤其项目开发周期比较长,可能三五年,再加上公司人员流动性大,原有代码的开发人员早就另谋高就,而代码写的更是一团糟,自带混淆,能走弯路不走直路。而现在需要在原有功能的基础上开发新功能,如果开闭原则使用得当的话,我们是不需要看懂原有代码实现细节便可以添加新代码实现新功能,毕竟有时候阅读一个功能的代码,比自己重新实现这个功能用的时间还要长。
## 三、示例
`假设我们使用出售电脑为例,首选定义一个顶层接口Computer:`
~~~
/**
* 顶层接口,定义了获取电脑信息的接口方法
*/
public interface Computer {
double getPrice();//价格
String getColor();//颜色
int getMemory();//内存
float getSize();//尺寸
}
~~~
`然后定义两个实现类,华硕电脑与苹果Mac`
~~~
/**
* 华硕
*/
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 String getColor() { return this.color; }
@Override
public int getMemory() { return this.memory; }
@Override
public float getSize() { return this.size; }
}
~~~
~~~
/**
* Mac
*/
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 String getColor() { return this.color; }
@Override
public int getMemory() { return this.memory; }
@Override
public float 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()
);
}
}
~~~
~~~
电脑:华硕
售价:4888.88
颜色:深蓝
内存:8
尺寸:14.0
~~~
这是我们一开始的需求,但是随着软件发布运行,我们需求不可能一成不变,肯定要接轨市场。
假设现在是双十一,需要搞促销活动。那么我们的代码肯定要添加新的功能。
可能有些刚入职的新人会在原有的代码上做改动:
~~~
@Override
public double getPrice() {
return this.price * 0.6;
}
~~~
这肯定不符合我们的开闭原则,虽然看起来这样做最直接,也最简单,但是绝大部分项目中,
一个功能的实现远比想像要复杂的多,我们在原有的代码中进行修改,其风险远比扩展和实现一个方法要大的多。
正确的做法可以这样:
~~~
/**
* 华硕电脑打折
*/
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()
);
}
}
~~~
~~~
电脑:华硕
原价:4888.88
售价:2444.44
颜色:深蓝
内存:8
尺寸:14.0
~~~
你可以看到如果想要调用getDiscountPrice()方法,在原有的基础上你还要对它进行强转,如果
能确定新扩展的需求,能兼容原有的继承体系,你也可以把它抽取到顶层的Computer的接口中。
最后看一下继承体系
![](https://img.kancloud.cn/ad/c5/adc5108a3e5d70ae6a9d294e9ca35c5f_829x653.png)
- 前言
- 第一章 设计七大原则
- 第1节 开闭原则
- 第2节 依赖倒置原则
- 第3节 单一职责原则
- 第4节 接口隔离原则
- 第5节 迪米特法则
- 第6节 里氏替换原则
- 第7节 合成复用原则
- 第二章 简单工厂模式
- 第1节 使用场景
- 第2节 示例代码
- 第三章 创建者模式
- 第1节 工厂方法模式
- 第2节 抽象工厂模式
- 第3节 建造者模式
- 第4节 原型模式
- 第5节 单例模式
- 第四章 结构型模式
- 第1节 适配器模式
- 第2节 桥接模式
- 第3节 组合模式
- 第4节 装饰者模式
- 第5节 外观模式
- 第6节 享元模式
- 第7节 代理模式
- 第五章 行为模式
- 第1节 责任链模式
- 第2节 命令模式
- 第3节 迭代器模式
- 第4节 中介者模式
- 第5节 备忘录模式
- 第6节 观察者模式
- 第7节 状态模式
- 第8节 策略模式
- 第9节 模板方法模式
- 第10节 访问者模式
- 第11节 解释器模式