一、开闭原则简介
开闭原则Open-Closed Principle,简称OCP,是面向对象的可复用设计中最重要的设计原则,具体定义如下:一个系统应当对扩展开放,对修改关闭,即软件系统应在不修改原有功能代码的情况下进行扩展(非必要时不轻易修改原有功能实现)。
遵循开闭原则的好处:
1、测试复用性:不影响原有的测试代码,只需对扩展部分的代码进行增量测试即可;
2、功能复用性:提高代码的复用性,避免陷入重复造轮子的倾向;
3、维护成本:提高系统的可维护性,降低运维成本;
二、如何使用开闭原则;
1、面对抽象编程:
1)在构建系统架构的时候,按接口类来划分功能,定义好接口的功能边界,与接口定义无关的功能不允许放在接口中;
2)参数以及引用对象一律使用接口,而不是实现类;
3)接口抽象层保持稳定,不允许随意修改;
2、元数据控制模块行为:
元数据(metadata)被定义为:描述数据的数据,对数据及信息资源的描述性信息。
通俗来说就是通过配置文件来操作数据,Spring容器的控制反转(Inversion of Control)就是一个典型的元数据控制模块行为的例子。
比较接近开发思维的解读就是:类似变量的定义,我们通过使用配置参数来操作功能和数据,而不是直接更改接口实现类。
3、约定优先:
约定优于配置,我们在一个项目中事先建立项目章程,使用章程中指定了所有开发人员都必须遵守的约定,避免在后续的开发过程中出现功能或其他规范的越界行为。
4、封装变化
封装变化,找出预计的变化或不稳定的点,为这些变化点创建稳定的接口,准确的讲是封装可能发生的变化。开闭原则只是一个理想的状态,它和物理学上“没有摩擦”很类似,任何系统都无法百分之百做到“开闭”。但是,只要我们要朝着这个方向前进,就可以显著的改善一个系统的架构,真正做到"拥抱变化"。
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口中;
第二,将不同的变化封装到不同的接口中,不应该有两个不同的变化出现在同一个接口或抽象类中。
三、案例分析
假设某系统有一个支付功能,现有的支付方式有支付宝、微信支付,后期可能还有银联支付、易支付等等,原始的设计方案如下:
// 客户端调用-选择支付手段
public class CommonPay {
void pay(String mode){
if(mode.equals("ali")){
AliPay aliPay = new AliPay();
aliPay.pay();
}else if(mode.equals("wx")){
WXPay wxPay = new WXPay();
wxPay.pay();
}
}
}
// 支付宝支付
public class AliPay {
public void pay() {
//TODO 调用支付宝支付
}
}
// 微信支付
public class WXPay{
public void pay() {
//TODO 调用微信支付
}
}
在上面的实现中,通过 commonPay.pay( 支付类型参数 ) 的方式传入不同支付类型参数来调用对应的支付功能,如果我们要扩展银联支付、易支付等,就要对 commonPay.pay 的实现进行修改,不符合开闭原则。
我们按如下的方式进行一次改进:
public interface Pay {
// 支付
void pay();
}
public class AliPay implements Pay {
@Override
public void pay() {
//TODO 调用支付宝支付
}
}
public class WXPay implements Pay{
@Override
public void pay() {
//TODO 调用微信支付
}
}
// 客户端调用-选择支付手段
public class CommonPay {
@Autowired
Pay payMode;
void pay(Pay payMode){
payMode.pay();
}
}
按这个模式,如果需要扩展银联支付,我们新增一个 UnionPay 的实现类,在CommonPay 的调用中传入payMode=UnionPay 对象给pay方法即可:
public class UnionPay implements Pay{
//TODO
}
END.