开闭原则(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 山吗?

5737 次点击
所在节点    程序员
60 条回复
x97bgt
2021-11-16 11:08:16 +08:00
@BeautifulSoap
@Rocketer
@silk
@wfd0807
@ykrank
@eason1874

如果是这样,那 OCP 有点难做到
- 如果兼容老接口是必须的,那肯定会一直堆东西,代码质量肯定会越来越糟糕。
- OCP 的前提是要有良好的抽象 /接口。但这需要精确地理解业务,同时要有良好的设计能力。抽象做不好,还不能改,那就会越走越偏了。。。这有点难。。

这些原则还是很博大精深的,什么时候要用,什么时候不用,纯粹靠经验了。。。
taowen
2021-11-16 11:10:33 +08:00
两个人改同一个文件容易 conflict 。如果是通过新增文件的方式来实现新需求,就可以减少 merge conflict 。
kop1989
2021-11-16 11:16:18 +08:00
@x97bgt #21
你论点的前提都有待商榷。

1 、为何“堆内容”就会导致代码质量“肯定”越来越糟糕???
2 、为何开闭原则要充分抽象?别忘了还有单一职责原则。

软件工程就是为了追求软件实现的最优性价比而存在的。
各种原则都有其利弊。你要抉择的是根据需求、成本等角度考虑保持原则的程度,而不是“用”与“不用”。
BeautifulSoap
2021-11-16 11:19:25 +08:00
@x97bgt 这东西就是个指导原则,你能做到就做,做不到也不会少块肉就是今后可能代码比较难维护。还有,开闭原则实现并不一定需要接口, 比如我们经常使用的各种 sort 函数就是很好遵循了开闭原则的函数(你要不同 sort 策略直接传个 callback 进去就好了)
eason1874
2021-11-16 11:19:32 +08:00
@x97bgt #21 开闭原则可以确保新旧版不会冲突,什么时候淘汰老版本是业务上的取舍,跟编程无关。

兼容只应该用于过渡,要设置淘汰周期。要是抱着老版本一直不放,那下场就跟 IE 一样。你不主动淘汰落后的技术,那现实就把你淘汰掉
x97bgt
2021-11-16 11:26:46 +08:00
@kop1989

1. shi 山不都是堆出来的吗?多搞些 if 和多用点继承(我能想到的扩展方式),那就会慢慢变得无法维护了。
2. 不抽象,那 client 处用什么方式来调用功能? 用用具体实现类?

不过确实,每个原则都是权衡的过程。没有一定要遵守的原则。
charlie21
2021-11-16 11:27:21 +08:00
x97bgt
2021-11-16 11:28:19 +08:00
@eason1874
所以遵循 OCP 的理由之一还要看 client 方是不是强势。如果是甲方,你不得不维护,那就就只能遵循了。
x97bgt
2021-11-16 11:28:53 +08:00
@BeautifulSoap
除了 sort 还有啥方式?我一开始能想到的就是实现接口和进行继承。
lingo
2021-11-16 11:40:57 +08:00
然而我觉得堆屎山确实是最终归宿 = =
BeautifulSoap
2021-11-16 11:41:26 +08:00
@x97bgt 也可以类似策略模式那样规定流程,然后可自由替换不同步骤的执行逻辑。或者让 client 自由组合不同方法实现功能(比如各种不同的 builder ,用得最多的应该就是 sql builder 了)
eason1874
2021-11-16 11:52:53 +08:00
@x97bgt 内部项目这样搞也很麻烦,改一处影响几十处,增加不必要的兼容风险,连累自己,连累同事

小打小闹的时候,工程规范像累赘,不灵活。随着工程复杂度增加,只有工程规范能把控风险,关键时候救你一命
lancerss
2021-11-16 11:57:43 +08:00
对扩展开放对修改关闭
Caturra
2021-11-16 12:28:27 +08:00
倒不如想想为什么要修改,是扩展做不到你想干的事情吗
libook
2021-11-16 12:29:11 +08:00
这个跟屎山没有关系,只是指导合理抽象的一种思路,避免不合理的抽象导致各种代码维护问题。

我试着举个例子吧,如果不贴切就当抛砖引玉了。

“modification”式的做法:写一个 class ,叫做“动物”:
有一个属性叫做“物种”,枚举为“狗”、“猫”;
有一个方法叫做“叫”。
“叫”方法里写一个判断逻辑:当“物种”为“狗” 的时候,输出“汪汪”;当“物种”为“猫”的时候,输出“喵喵”。
后面需求发生了变化,想要增加一个新物种“鸭子”,那么只能够去修改“动物”这个 class ,在“物种”这个枚举里添加“鸭子”,在“叫”这个方法里添加一个新的判断分支,当“物种”为“鸭子”的时候输出“嘎嘎”。

“extension”式的做法:写一个 interface ,叫做“动物”:
有一个属性叫做“物种”,
有一个方法叫做“叫”。
基于“动物”实现一个“狗”class ,“物种”属性为“狗”,“叫”方法为输出“汪汪”;
基于“动物”实现一个“猫”class ,“物种”属性为“猫”,“叫”方法为输出“喵喵”;
后面需求发生了变化,想要增加一个新物种“鸭子”,可以在不修改“动物”代码的基础上直接基于“动物”实现一个“鸭子”class ,“物种”属性为“鸭子”,“叫”方法为输出“嘎嘎”。
kujio
2021-11-16 12:51:27 +08:00
差不多就是可以 copy 一个分支,让部分功能走新的分支,但不能修改原有的代码,
以这种类似 java 重载的方式来实现修改,
局限是有的,有时候确实麻烦,但可以保持代码兼容性.
dddd1919
2021-11-16 12:54:53 +08:00
改了重写 ×
继承扩展 √
flniu
2021-11-16 13:02:18 +08:00
软件实体应该对扩展开放、对修改封闭。
有一个直观的理解就是,该实体相关的单元测试,应该只添加(扩展新逻辑)、不修改(修改现有逻辑)。
然后在不破坏单元测试(不破坏约定的外部行为)的前提下,可以经常重构,不断重构。
再结合单一职责原则,软件实体可以被替换、退休,但确实不应该修改。
unco020511
2021-11-16 14:09:28 +08:00
意思是你写的工具,别人用的时候可以很方便的通过继承 /实现 /组合等等这样的方式来扩展功能,并不需要去修改你原来的代码
otakustay
2021-11-16 14:22:30 +08:00
@silk #14 了解不够那不叫设计,那叫瞎比划

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

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

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

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

© 2021 V2EX