开闭原则(open/closed principle)到底是啥意思?

2021-11-16 10:33:46 +08:00
 x97bgt

SOLID 的提出者 Martin 是这么描述开闭原则 OCP 的

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

但我不大理解。代码一旦写完,就不能修改,只能往上添东西了?这不是堆 shi 山吗?

5720 次点击
所在节点    程序员
60 条回复
zongren
2021-11-16 10:38:27 +08:00
这。。。企业及理解了
我希望我发布的类不允许被 client 魔改,而只能继承并使用已有的方法,或新增方法
powerfulyang
2021-11-16 10:39:10 +08:00
你用别人库的时候,希望不同版本自己都重新适配一遍?
x97bgt
2021-11-16 10:40:29 +08:00
@zongren
啥叫被 client 魔改?
silk
2021-11-16 10:41:18 +08:00
不能修改的是抽象基础吧,具体需求实现是基于这之上的
x97bgt
2021-11-16 10:42:40 +08:00
@powerfulyang
那只要接口不变,就不需要重新适配了啊。跟这段话有什么关系? 你修改或新增代码,都可以做到兼容老接口啊。
bk201
2021-11-16 10:43:23 +08:00
保持兼容性的意思
silk
2021-11-16 10:43:24 +08:00
就好像 实现一个人 他要吃饭睡觉 这是基础的,至于说吃什么 睡在那里都属于可以修改的。而且吃饭睡觉也只是从人这个基础衍生的具体行为。套娃了
x97bgt
2021-11-16 10:43:54 +08:00
@silk 你这个描述可以说得过去。但这句话完全没有提到抽象、接口什么的,感觉对不上。
silk
2021-11-16 10:45:15 +08:00
人是一个抽象的类 人的行为是你要实现的接口
ENNRIaaa
2021-11-16 10:46:48 +08:00
对扩展开放,对修改关闭
BeautifulSoap
2021-11-16 10:48:01 +08:00
楼上说复杂了,这里 clinet 指的是使用你定义好的类或函数的那一方。对于使用你的类或函数的那一方,你应该提供一定机制可以让他们方便扩展功能(比如提供改变执行策略的接口啊,继承啊之类的),而不应该是他们想要什么功能了,因为没法扩展实现功能,而直接跑过来直接去改你定义好的类或函数的代码
Rocketer
2021-11-16 10:48:11 +08:00
因为修改一个地方(类、方法等)很可能会导致调用方跟着修改,这是麻烦且不可预期的,很容易导致故障,所以禁止修改。

但你也不应禁止别人在你的基础上扩展,否则只能用你提供的功能就太局限了。

这是个指导原则,脑子里时刻想着这些,你在设计代码结构的时候就会更优雅。各种设计模式几乎都是这个原则的优秀实践。
otakustay
2021-11-16 10:49:35 +08:00
一开始是个 A ,然后有一个需要 B ,完了之后 B 不需要了,又新增了需求 C

在修改的方案上,A 加代码变成 AB ,AB 加代码变成 ABC
此时应该有一个把 AB 删代码变回 A 的过程,但只要项目紧急些就不会干这事,最后就是 ABCDEFG 一堆了

用扩展的方案,我们会设计一个有扩展性的 A ,然后有一个独立的 B ,使用 A+B 。之后再有一个独立的 C ,使用 A+C 的组合
这样做的几个优势:
1. B 是独立的,删除它是零成本。甚至不管它,它也不会影响其它的部件,并不导致系统复杂性的提升和可维护性的降低
2. 配合良好的部署架构,A+C 和 B+C 可以做增量更新和热更新,但需要修改 A 代码变成 AB 的方案就几乎无法做增量更新
3. 更极端的情况下,如果需要一部分用户是 A+B ,另一部分用户是 A+C ,扩展的方法可以保持结构更清晰。对于类似前端的情况,也可以做到向用户传输的内容更少

扩展方案的代价就是,你需要 A 能支持往上面插 B 、C 、D ,就必须对 A 有一个足够抽象的设计
silk
2021-11-16 10:51:39 +08:00
#13 必须对 XXX 有一个足够抽象的设计 这是关键 不然还不如堆 shi 山
x97bgt
2021-11-16 10:53:16 +08:00
@BeautifulSoap
@Rocketer
那就是定义好接口。有改动时,保证接口的兼容性(扩展),以做到不需要 client 修改调用方式。

但是这里的适用对象是什么呢?是同一个包里的不同类,不同包,还是不同的 lib 之间?感觉尺度不一样,这个原则就比较微妙。啥时候应该应用?
wfd0807
2021-11-16 10:53:47 +08:00
不要纠结于字面意思,你在自己的工作过程中记住一点:在完成当前需求的情况下,尽可能多的考虑未来可能发生的变化,并把这些变化作为当前需求的一部分;随着经验的累积,你会自然而然的熟练运用开闭原则
ykrank
2021-11-16 10:56:08 +08:00
这个是原则。而接口,抽象都是为了实现这个原则的产物。什么叫接口,接口就是提取开闭原则里面开的那部分;什么是实现,实现就是开闭原则里闭的那部分。
如果说开闭原则是宪法,那接口这个定义只是根据宪法制定的法律而已。宪法肯定不会去提具体的法律名称,因为那些定义只是从自己引申出来的孙子而已。
wfd0807
2021-11-16 10:56:36 +08:00
@wfd0807 “并把这些变化作为当前需求的一部分” => 并不是在当前需求中实现,而且在当前需求中留下扩展的余地
eason1874
2021-11-16 10:58:20 +08:00
不是不让你 debug ,而是不让修改功能(行为),保持一致性

如果你在功能实现上就是屎一样,想改功能,那你就新增一个,而不是直接同名替换

同名替换会导致牵一发而动全身,所有内部的外部的调用都需要重新适配
ykrank
2021-11-16 11:00:46 +08:00
你在谈接口这个定义的同时,其实就是在谈论开闭原则的具体实现。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/815704

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX