开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
强调的是用抽象构建框架,用实现扩展细节。
开闭原则,是面向对象设计中最基础的设计原则。它指导咱们如何创建稳定灵活的系统。
例如:咱们版本更新,尽量不修改原代码,可是能够增长新功能。框架
开闭原则要求咱们经过保持原有代码不变,添加新代码来实现软件的变化,由于不涉及原代码的改动,这样能够避免为实现新功能而改坏线上功能的状况,避免老用户的流失。ide
软件开发规范性好的团队都会写单元测试,若是原有的某个功能发生了变化,则单元测试代码也应作相应的变动,不然就有可能致使测试出错。若是每次软件的变化,除了变动功能代码以外,还得变动测试代码,书写测试代码一样须要消耗工时,这样在项目中引入单元测试就成了累赘。开闭原则可让单元测试充分发挥做用而又不会成为后期软件开发的累赘。模块化
开闭原则可让代码中的各功能,以及新旧功能独立存在于不一样的单元模块中,一旦某个功能出现问题,能够很快地锁定代码位置做出修改,因为模块间代码独立不相互调用,更改一个功能的代码也不会引发其余功能的崩溃。函数
在项目开发过程当中,有时候阅读前人的代码是件很头疼的事,尤为项目开发周期比较长,可能三五年,再加上公司人员流动性大,原有代码的开发人员早就另谋高就,而代码写的更是一团糟,自带混淆,能走弯路不走直路。而如今须要在原有功能的基础上开发新功能,若是开闭原则使用得当的话,咱们是不须要看懂原有代码实现细节即可以添加新代码实现新功能,毕竟有时候阅读一个功能的代码,比本身从新实现这个功能用的时间还要长。单元测试
假设咱们使用出售电脑为例,首选定义一个顶层接口Computer:测试
/** * Create By Ke Shuiqiang 2020/3/4 17:12 * 顶层接口,定义了获取电脑信息的接口方法 */ public interface Computer { double getPrice();//价格 String getColor();//颜色 int getMemory();//内存 float getSize();//尺寸 }
而后定义两个实现类,华硕电脑与苹果Macui
/** * Create By Ke Shuiqiang 2020/3/4 17:20 * 华硕 */ 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; } }
/** * Create By Ke Shuiqiang 2020/3/4 17:17 * 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; } }
测试类:this
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
这是咱们一开始的需求,可是随着软件发布运行,咱们需求不可能一成不变,确定要接轨市场。假设如今是双十一,须要搞促销活动。那么咱们的代码确定要添加新的功能。
可能有些刚入职的新人会在原有的代码上作改动:spa
@Override public double getPrice() { return this.price * 0.6; }
这确定不符合咱们的开闭原则,虽然看起来这样作最直接,也最简单,可是绝大部分项目中,一个功能的实现远比想像要复杂的多,咱们在原有的代码中进行修改,其风险远比扩展和实现一个方法要大的多。
正确的作法能够这样:设计
/** * Create By Ke Shuiqiang 2020/3/4 17:50 * 华硕电脑打折 */ 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的接口中。
最后看一下继承体系
上面只是一个很简单的示例,在实际开发过程当中,并非必定要求全部代码都遵循设计原则,咱们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助咱们设计出更加优雅的代码结构。