开闭原则(OCP)核心定义讲解 支付宝支付类图实例解析-CSDN博客
做后端开发的朋友,多半都听过程序设计的六大设计原则,开闭原则就是其中最核心的一个,甚至很多人说它是所有设计原则的根。不少新手刚接触的时候,总觉得这个概念太抽象,看不懂说的到底是什么,今天咱们就用最直白的话讲清楚定义,再结合支付宝支付的实际例子,把它拆明白。
先来说核心定义,开闭原则英文是Open Close Principle,简称OCP,它的核心说法是“对扩展开放,对修改关闭”。刚看到这句话的时候,很多人会懵,什么叫开什么叫关?
其实换个生活化的说法你就能懂,比如你去奶茶店办了一张储值卡,原来只能买珍珠奶茶,现在店家出了新的果茶,不需要你重新办卡,直接用原来的储值卡就能买,这就是对扩展开放——加新产品不用动原来的卡。反过来,你不用因为出了新产品,就把原来的卡退掉重办,这就是对修改关闭——不用改已经写好、跑稳的老代码。
放到开发里说,就是当项目有新需求来了,我们尽量不要去改已经在跑的、测试过的核心老代码,而是通过加新代码的方式实现新功能。为什么要这么做?其实道理很简单,老代码已经跑了很久,线上也没出问题,你乱改它,一不小心就会带出新bug,改完还要重新全量测试,既费时间又有风险。加新代码就不一样,老逻辑不动,只测新逻辑就行,风险小很多。
我刚接触这个原则的时候,总觉得说起来容易做起来难,真到开发的时候,需求变了不还得改老代码吗?后来做了几年项目才发现,不是绝对不能改老代码,是我们要提前做好设计,让需求变化的时候,尽量只加新代码,少改甚至不改老代码。
接下来咱们拿实际项目里最常见的支付宝支付功能做例子,一步步画类图拆解,看完你就懂了。
假设我们现在做一个电商项目,一开始只需要支持支付宝支付,很多新手写代码,会直接写出这么一个类:
先建一个Alipay类,里面写一个pay方法,处理支付的参数签名、调用支付宝接口、处理回调这些逻辑。然后在订单服务OrderService里,直接调用Alipay类的pay方法,流程走通,项目上线,看起来一切都没问题。
这时候运营提需求了,说现在用户想要微信支付,也得加上。那新手最直接的做法是什么?就是在OrderService里加判断,如果用户选支付宝就调用Alipay,如果选微信就新建一个WechatPay类,然后加个if else分支调用WechatPay的pay方法。
你看,这就出问题了——原来已经跑稳的OrderService改了,原来测试好的支付逻辑,你加了新分支,就得重新测一遍整个订单支付流程,万一不小心改到原来支付宝的逻辑,直接把线上功能弄崩,这都是真实项目里常发生的事。如果之后再加银联支付、云闪付支付,你还要继续改OrderService,加更多if else,类会越来越臃肿, bug概率越来越高。
这就是典型的没遵循开闭原则的写法,每次加新支付方式都要改已经写好的老代码。那我们按照开闭原则重新设计一下,看看会变成什么样。
首先,我们抽象出来一个支付的顶层接口Payment,接口里只定义一个统一的pay方法,所有的支付方式都实现这个接口。那具体就是Alipay实现Payment接口,写自己的支付宝支付逻辑;WechatPay也实现Payment接口,写自己的微信支付逻辑。
接下来,OrderService不用再自己写判断挑支付方式了,我们加一个支付工厂类PayFactory,根据前端传过来的支付类型,返回对应的Payment实例就行。OrderService只需要拿到Payment实例,调用pay方法就行,根本不用管这个实例到底是支付宝还是微信的。
现在如果要加新的支付方式UnionPay,我们只需要做两件事:第一,新建一个UnionPay类实现Payment接口,写好自己的支付逻辑;第二,在PayFactory里加一个类型判断,返回UnionPay的实例就行。原来的Alipay、WechatPay、OrderService的代码一行都不用改,完全符合“对扩展开放,对修改关闭”的要求。
这个结构的类图其实也很清晰,最顶层是抽象接口Payment,然后多个具体支付类Alipay、WechatPay、UnionPay都实现这个接口,PayFactory依赖Payment接口,OrderService依赖PayFactory和Payment接口。这里就能看出来,开闭原则的实现,其实依赖了另一个设计原则——依赖倒置,就是面向接口编程,不要面向实现编程,没有抽象接口的提前定义,也做不到开闭。
可能有人会说,加了这么多类,会不会更麻烦?其实一点都不,现在加新支付,只需要加新类,改工厂里一点点判断,原来跑稳的代码完全不动,风险小太多了。哪怕以后要删掉某个支付方式,直接删掉对应的类就行,不影响其他的支付逻辑。
刚才这个例子里,PayFactory其实还是改了一点点,有没有办法连工厂都不用改?其实也可以,用反射或者配置文件,把支付类型和对应的类全路径存在配置里,加新支付的时候,只需要加类,再在配置文件里加一行配置就行,工厂代码完全不用改,这就是更彻底的遵循开闭原则。不过大部分场景下,不需要做这么极致,适合项目的才是最好的。
说了这么多,可能有人会问,我们开发天天赶需求,为什么一定要花时间遵循开闭原则?我总结一下实际项目里的好处。
第一个就是降低线上风险,老代码不动,新代码单独测试,不会影响原来的稳定功能,尤其是大型项目,牵一发动全身,改老代码的成本真的很高。第二个就是代码可维护性好,每个支付方式都在自己的类里,不会出现几百行的if else堆在一个方法里,新人接手也能看懂。第三个就是复用性好,抽象出来的接口,其他模块要用支付功能,直接用就行,不用再重新写一遍。
当然也不是说,所有地方都要硬套开闭原则,如果只是一个内部用的小工具,需求一辈子可能就变一次,你硬要拆出一堆接口类,反而过度设计,浪费时间。开闭原则主要用在那些需求容易变、长期维护的核心模块上,比如支付、优惠券、各种渠道对接这些地方,提前做好抽象,后面加新需求的时候,你就能省很多事。
很多新手觉得设计原则都是虚的,都是大佬们吹出来的,其实真做过几个长期维护的项目就知道,提前遵循这些原则,少改几次线上bug,少加班几次,你就明白它的价值了。开闭原则也不是说让你绝对不能改老代码,它是一种思维方式,让你在做设计的时候,多想想未来可能的变化,提前留好扩展的口子,不用每次改需求都动根基,这就是它最核心的意义。
开闭原则, OCP, 设计原则, 开闭原则定义, 类图实例, 支付宝支付, OCP实例, 设计模式, 后端开发, 面向对象设计
[Q]:什么是开闭原则(OCP)?
[A]:开闭原则的核心定义是「对扩展开放,对修改关闭」,简单来说就是新增功能时尽量加新代码,少改已经测试稳定的老代码,降低改出bug的风险。
[Q]:开闭原则里的「对扩展开放,对修改关闭」是什么意思?
[A]:对扩展开放指允许新增功能逻辑,对修改关闭指不需要修改已经运行稳定的原有核心代码,用新增代码而非修改老代码实现新需求。
[Q]:为什么开发要遵循开闭原则?
[A]:遵循开闭原则可以降低修改老代码带来的线上风险,提升代码的可维护性和复用性,减少重复测试的工作量,适合长期维护的项目。
[Q]:不遵循开闭原则会有什么问题?
[A]:新增功能时频繁修改老代码,容易不小心引入新bug,而且会让代码变得臃肿难维护,每次改需求都要重新测试全量逻辑,效率低风险高。
[Q]:支付宝支付场景为什么适合用来讲解开闭原则?
[A]:支付功能是项目里非常常见的、需求容易扩展的场景,很容易对比出遵循开闭原则和不遵循的差异,更容易理解OCP的实际用法。
[Q]:按照开闭原则设计支付宝支付功能要怎么做?
[A]:先抽象统一支付顶层接口,让不同支付方式分别实现这个接口,订单服务只依赖抽象支付接口,新增支付方式时只需要新增对应实现类,不需要修改原有核心代码。
[Q]:所有代码都需要遵循开闭原则吗?
[A]:不需要,开闭原则更适合需求容易变化、需要长期维护的核心模块,小工具、一次性项目过度遵循开闭原则反而会造成过度设计,浪费开发时间。
[Q]:实现开闭原则需要依赖什么设计思想吗?
[A]:实现开闭原则核心要依赖面向接口编程(依赖倒置原则),提前抽象出顶层规范,才能让新增功能只扩展不修改原有代码。
先来说核心定义,开闭原则英文是Open Close Principle,简称OCP,它的核心说法是“对扩展开放,对修改关闭”。刚看到这句话的时候,很多人会懵,什么叫开什么叫关?
其实换个生活化的说法你就能懂,比如你去奶茶店办了一张储值卡,原来只能买珍珠奶茶,现在店家出了新的果茶,不需要你重新办卡,直接用原来的储值卡就能买,这就是对扩展开放——加新产品不用动原来的卡。反过来,你不用因为出了新产品,就把原来的卡退掉重办,这就是对修改关闭——不用改已经写好、跑稳的老代码。
放到开发里说,就是当项目有新需求来了,我们尽量不要去改已经在跑的、测试过的核心老代码,而是通过加新代码的方式实现新功能。为什么要这么做?其实道理很简单,老代码已经跑了很久,线上也没出问题,你乱改它,一不小心就会带出新bug,改完还要重新全量测试,既费时间又有风险。加新代码就不一样,老逻辑不动,只测新逻辑就行,风险小很多。
我刚接触这个原则的时候,总觉得说起来容易做起来难,真到开发的时候,需求变了不还得改老代码吗?后来做了几年项目才发现,不是绝对不能改老代码,是我们要提前做好设计,让需求变化的时候,尽量只加新代码,少改甚至不改老代码。
接下来咱们拿实际项目里最常见的支付宝支付功能做例子,一步步画类图拆解,看完你就懂了。
假设我们现在做一个电商项目,一开始只需要支持支付宝支付,很多新手写代码,会直接写出这么一个类:
先建一个Alipay类,里面写一个pay方法,处理支付的参数签名、调用支付宝接口、处理回调这些逻辑。然后在订单服务OrderService里,直接调用Alipay类的pay方法,流程走通,项目上线,看起来一切都没问题。
这时候运营提需求了,说现在用户想要微信支付,也得加上。那新手最直接的做法是什么?就是在OrderService里加判断,如果用户选支付宝就调用Alipay,如果选微信就新建一个WechatPay类,然后加个if else分支调用WechatPay的pay方法。
你看,这就出问题了——原来已经跑稳的OrderService改了,原来测试好的支付逻辑,你加了新分支,就得重新测一遍整个订单支付流程,万一不小心改到原来支付宝的逻辑,直接把线上功能弄崩,这都是真实项目里常发生的事。如果之后再加银联支付、云闪付支付,你还要继续改OrderService,加更多if else,类会越来越臃肿, bug概率越来越高。
这就是典型的没遵循开闭原则的写法,每次加新支付方式都要改已经写好的老代码。那我们按照开闭原则重新设计一下,看看会变成什么样。
首先,我们抽象出来一个支付的顶层接口Payment,接口里只定义一个统一的pay方法,所有的支付方式都实现这个接口。那具体就是Alipay实现Payment接口,写自己的支付宝支付逻辑;WechatPay也实现Payment接口,写自己的微信支付逻辑。
接下来,OrderService不用再自己写判断挑支付方式了,我们加一个支付工厂类PayFactory,根据前端传过来的支付类型,返回对应的Payment实例就行。OrderService只需要拿到Payment实例,调用pay方法就行,根本不用管这个实例到底是支付宝还是微信的。
现在如果要加新的支付方式UnionPay,我们只需要做两件事:第一,新建一个UnionPay类实现Payment接口,写好自己的支付逻辑;第二,在PayFactory里加一个类型判断,返回UnionPay的实例就行。原来的Alipay、WechatPay、OrderService的代码一行都不用改,完全符合“对扩展开放,对修改关闭”的要求。
这个结构的类图其实也很清晰,最顶层是抽象接口Payment,然后多个具体支付类Alipay、WechatPay、UnionPay都实现这个接口,PayFactory依赖Payment接口,OrderService依赖PayFactory和Payment接口。这里就能看出来,开闭原则的实现,其实依赖了另一个设计原则——依赖倒置,就是面向接口编程,不要面向实现编程,没有抽象接口的提前定义,也做不到开闭。
可能有人会说,加了这么多类,会不会更麻烦?其实一点都不,现在加新支付,只需要加新类,改工厂里一点点判断,原来跑稳的代码完全不动,风险小太多了。哪怕以后要删掉某个支付方式,直接删掉对应的类就行,不影响其他的支付逻辑。
刚才这个例子里,PayFactory其实还是改了一点点,有没有办法连工厂都不用改?其实也可以,用反射或者配置文件,把支付类型和对应的类全路径存在配置里,加新支付的时候,只需要加类,再在配置文件里加一行配置就行,工厂代码完全不用改,这就是更彻底的遵循开闭原则。不过大部分场景下,不需要做这么极致,适合项目的才是最好的。
说了这么多,可能有人会问,我们开发天天赶需求,为什么一定要花时间遵循开闭原则?我总结一下实际项目里的好处。
第一个就是降低线上风险,老代码不动,新代码单独测试,不会影响原来的稳定功能,尤其是大型项目,牵一发动全身,改老代码的成本真的很高。第二个就是代码可维护性好,每个支付方式都在自己的类里,不会出现几百行的if else堆在一个方法里,新人接手也能看懂。第三个就是复用性好,抽象出来的接口,其他模块要用支付功能,直接用就行,不用再重新写一遍。
当然也不是说,所有地方都要硬套开闭原则,如果只是一个内部用的小工具,需求一辈子可能就变一次,你硬要拆出一堆接口类,反而过度设计,浪费时间。开闭原则主要用在那些需求容易变、长期维护的核心模块上,比如支付、优惠券、各种渠道对接这些地方,提前做好抽象,后面加新需求的时候,你就能省很多事。
很多新手觉得设计原则都是虚的,都是大佬们吹出来的,其实真做过几个长期维护的项目就知道,提前遵循这些原则,少改几次线上bug,少加班几次,你就明白它的价值了。开闭原则也不是说让你绝对不能改老代码,它是一种思维方式,让你在做设计的时候,多想想未来可能的变化,提前留好扩展的口子,不用每次改需求都动根基,这就是它最核心的意义。
开闭原则, OCP, 设计原则, 开闭原则定义, 类图实例, 支付宝支付, OCP实例, 设计模式, 后端开发, 面向对象设计
[Q]:什么是开闭原则(OCP)?
[A]:开闭原则的核心定义是「对扩展开放,对修改关闭」,简单来说就是新增功能时尽量加新代码,少改已经测试稳定的老代码,降低改出bug的风险。
[Q]:开闭原则里的「对扩展开放,对修改关闭」是什么意思?
[A]:对扩展开放指允许新增功能逻辑,对修改关闭指不需要修改已经运行稳定的原有核心代码,用新增代码而非修改老代码实现新需求。
[Q]:为什么开发要遵循开闭原则?
[A]:遵循开闭原则可以降低修改老代码带来的线上风险,提升代码的可维护性和复用性,减少重复测试的工作量,适合长期维护的项目。
[Q]:不遵循开闭原则会有什么问题?
[A]:新增功能时频繁修改老代码,容易不小心引入新bug,而且会让代码变得臃肿难维护,每次改需求都要重新测试全量逻辑,效率低风险高。
[Q]:支付宝支付场景为什么适合用来讲解开闭原则?
[A]:支付功能是项目里非常常见的、需求容易扩展的场景,很容易对比出遵循开闭原则和不遵循的差异,更容易理解OCP的实际用法。
[Q]:按照开闭原则设计支付宝支付功能要怎么做?
[A]:先抽象统一支付顶层接口,让不同支付方式分别实现这个接口,订单服务只依赖抽象支付接口,新增支付方式时只需要新增对应实现类,不需要修改原有核心代码。
[Q]:所有代码都需要遵循开闭原则吗?
[A]:不需要,开闭原则更适合需求容易变化、需要长期维护的核心模块,小工具、一次性项目过度遵循开闭原则反而会造成过度设计,浪费开发时间。
[Q]:实现开闭原则需要依赖什么设计思想吗?
[A]:实现开闭原则核心要依赖面向接口编程(依赖倒置原则),提前抽象出顶层规范,才能让新增功能只扩展不修改原有代码。
评论 (0)
