单一职责原则:https://blog.csdn.net/dmk877/article/details/143447010
在前面我们学习了单一职责原则,今天来一起学习一下SOLID原则中的开闭原则(Open-Closed Principle, OCP)
通过本篇博客你将学到到以下内容
①什么是开闭原则
②如何实现开闭原则
③两个开闭原则的案例
一、什么是开闭原则
首先我们来看下开闭原则的定义:
Software entities like classes,modules and functions should be open for extension but closed for modifications.
软件实体如类、模块和函数应该对扩展开放,对修改关闭。
怎么理解这句话呢?这句话最重要的就是"对扩展开放,对修改关闭"
- 对扩展开放:当有新的需求或变化时,可以对现有代码进行扩展,以适应新的需求
- 对修改关闭:需求一旦开发完成,就可以独立完成其工作,而不要对已有代码做修改
二、如何实现开闭原则
一般用来提高扩展性的方法有:多态、依赖注入、面向抽象而非面向具体编程,怎么利用多态、依赖注入、面向抽象而非具体编程,来实现“对扩展开放、对修改关闭”呢?接下来我举两个例子,相信通过这两个例子你对开闭原则的理解会更加深入。
2.1 举例一
什么依赖注入,什么是面向抽象而非具体的编程
// 将图形进行抽象
public interface Shape {//..}
// 圆形
public class Cirlce implements Shape {//..}
// 三角形
public class Triangle implements Shape {//..}
// 长方形
public class Rectangle implements Shape {//..}
public class Demo {
private Shape shape; // 基于接口而非实现的编程
// 依赖注入
public Demo(Shape shape) {
this.shape = shape;
}
}
以上这段伪代码就是基于接口而非具体编程,将图形共同的属性和方法抽取到Shape接口中,然后针对不同的图形会分别实现接口中的功能。那么问题来了,为什么要这么做呢?来一个完整的例子,通过这个例子你就会明白为什么要这么做
2.2 举例二 电商支付系统
假如我们在开发电商平台的支付系统,当前支付的方式有Alipay、WechatPay,这个支付系统如何设计呢?首先可以定义个PayManager来统一管理支付。代码如下
public class PayManager {
public void pay(int payMode) {
if (payMode == 1) {
aliPay();
} else if (payMode == 2) {
wechatPay();
}
}
private void aliPay() {
System.out.println("调用 alipay 接口");
}
private void wechatPay() {
System.out.println("调用 wechatpay 接口");
}
}
可以看到在PayManager的pay方法里根据传递的参数来判断应该使用哪种支付方式,貌似没啥问题,但是随着业务的发展需要增加银行卡支付方式BankCardPay,应该怎么处理呢?需要修改两个地方
- pay方法里增加一个if分支
- 写一个bankcardPay方法
代码如下
public class PayManager {
public void pay(int payMode) {
if (payMode == 1) {
aliPay();
} else if (payMode == 2) {
wechatPay();
} else if (payMode == 3) {
// 修改点一:增加一个if分支
bankcardPay();
}
}
private void aliPay() {
System.out.println("调用 alipay 接口");
}
private void wechatPay() {
System.out.println("调用 wechatpay 接口");
}
// 修改点二:增加一个bankcardPay方法
private void bankcardPay() {
System.out.println("调用 bankcardpay 接口");
}
}
有没有发现一个问题,PayManager里的pay方法进行了修改,假如后续还有其它支付方式的增加是不是每次都要增加一个if语句呢?这么做有什么弊端呢?
上述修改方式对现有的代码进行了修改,有潜在的危险,因为我们的pay方法在新增支付方式之前已经测试过并上线,你新增了一个支付方式对其进行了修改,是不是还要重新测试一遍呢?
另外这种写法其实违背了开闭原则即“对扩展开放,对修改关闭”,哪里违背了呢?其实就是对扩展支持的不好,新增一种支付方式需要修改核心代码逻辑风险很大。那么我们应该怎么去设计它呢?案例一中可以了解到什么是面向抽象而非具体编程,同样Shape接口一样是不是可以对支付方式进行抽象呢?当然,可以定义一个接口Payment在其中抽象一个pay方法用来完成支付功能,它的代码如下
public interface Payment {
public void pay();
}
所有的支付方式都需要实现此接口,当前只支持AliPay和WechatPay这两种支付方式,它们的代码如下
public class AliPay implements Payment {
@Override
public void pay() {
System.out.println("调用 alipay 接口");
}
}
public class WechatPay implements Payment {
@Override
public void pay() {
System.out.println("调用 WechatPay 接口");
}
}
PayManager的代码如下
public class PayManager {
// 依赖注入
public void pay(Payment payment) {
payment.pay();
}
}
然后就是如何去调用
public class TestPay {
public static void main(String[] args) {
Payment wechatPay = new WechatPay();
PayManager.pay(wechatPay);
}
}
可以看到第三行需要哪种支付方式直接创建其对象并将其传递个PayManager的pay方法即可。此时需要增加一个银行卡支付功能应该如何处理呢?只需要增加一个BankCardPay类并实现Payment接口即可,代码如下
public class BankCardPay implements Payment {
@Override
public void pay() {
System.out.println("调用 BankCardPay 接口");
}
}
这样就已经修改完成,调用方法也跟上面一样,代码如下
public class TestPay {
public static void main(String[] args) {
Payment wechatPay = new BankCardPay();
PayManager.pay(wechatPay);
}
}
可以看到增加一种BankCardPay支付方式并未对PayManager类做修改,因此对之前已经上线的功能没有影响。后续再增加其它支付方式比如京东支付、抖音支付等等都很容易扩展。
可能有些同学会说,这样修改增加很多个类可读性还没有之前好,确实如此,有些情况下代码的扩展性会跟可读性相冲突,比如上面我们重构代码之后,可读性没有之前的if分支好,所以很多时候我们要在扩展性和可读性之间做权衡,在某些场景下代码的可读性很重要,我们就牺牲一些扩展性,在某些场景下代码的扩展性很重要,我们就牺牲一些可读性。
比如上述的例子,如果项目一开始就说我们的支付方式只支持Alipay和WechatPay这两种支付方式,那刚开始的写法思路简单易读,它就是合理的。相反如果后续要增加很多种支付方式,那么我们重构之后的写法就是比较合理的,因此是否合理没有一个统一的标准,一定要结合业务需求、场景等来进行重构。
三、总结
1.定义
软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代码来完成新的功能开发,低层模块的变更,必然要有高层模块进行耦合,否则就是一个独立无意义的代码片段。
2.为什么要使用开闭原则
(1)对于测试来讲,新增的方法不会对已有的方法造成影响,只需要保证新增的类、模块和函数是正确的就可以了
(2)提高可复用性,在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用,粒度越小,被复用的可能性就越大。
(3)提高可维护性,从上述例子中可以看出来当对已有功能做修改时增加一个类即可,这是不是就是维护人员很乐意干的事,即增加一个类,而不是修改一个类。
3.如何做到"对扩展开放、对修改关闭"
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。我们要时刻具备扩展意识、抽象意识、封装意识,在写代码的时候要多花点时间思考一下,未来可能的变更,以便在未来需求变更的时候,在不改变代码整体结构的情况下,将新的代码灵活的插入到项目中。
很多设计原则、设计思想、设计模式都是以提到代码的扩展性为最终目的。特别是23中经典设计模式,大部分都是为了解决代码的扩展性而总结出来的,都是以开闭原则为指导原则。最常用提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(装饰、策略、模版、责任链、状态)
好了本篇博客就到这里了,后续还会继续更新关于设计原则和设计模式相关的文章,如果觉得对你有用,帮忙点赞回复666
参考书籍:
《设计模式之禅》
《架构整洁之道》