后端技术面试 38 讲
李智慧
同程艺龙交通首席架构师,前 Intel& 阿里架构师,《大型网站技术架构》作者
37373 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 46 讲
不定期加餐 (1讲)
后端技术面试 38 讲
15
15
1.0x
00:00/00:00
登录|注册

11丨软件设计的开闭原则:如何不修改代码却能实现需求变更?

在父类中用抽象方法定义计算的骨架和过程
一对多的对象依赖关系
实现开闭原则
使Dialer类保持不变
将两个不匹配的接口适配起来
实现开闭原则
Button依赖ButtonServer,Dialer实现ButtonServer
Button和Dialer之间增加抽象接口ButtonServer
多个策略实现同一个策略接口
难以复用
代码腐坏
需要不断修改按钮类代码
违反开闭原则
DigitButtonDialerAdapter和SendButtonDialerAdapter的存在
应该时刻以开闭原则指导、审视设计
开闭原则是软件设计的核心原则
实现开闭原则的关键是抽象
实现开闭原则
具体类型的按钮实现这个方法
在Button类中定义抽象方法onPress
模板方法模式
实现开闭原则
目标设备拨号器、密码锁等作为观察者
Button作为被观察者
观察者模式
增加适配器DigitButtonDialerAdapter、SendButtonDialerAdapter
适配器模式
对上面的例子重新进行设计
策略模式
按钮类关联拨号器类的设计
软件实体应该对扩展是开放的,对修改是关闭的
思考题
小结
使用模板方法模式实现开闭原则
使用观察者模式实现开闭原则
使用适配器模式实现开闭原则
使用策略模式实现开闭原则
一个违反开闭原则的例子
开闭原则
软件设计的开闭原则

该思维导图由 AI 生成,仅供参考

我在上篇文章讲到,软件设计应该为需求变更而设计,应该能够灵活、快速地满足需求变更的要求。优秀的程序员也应该欢迎需求变更,因为持续的需求变更意味着自己开发的软件保持活力,同时也意味着自己为需求变更而进行的设计有了用武之地,这样的话,技术和业务都进入了良性循环。
但是需求变更就意味着原来开发的功能需要改变,也意味着程序需要改变。如果是通过修改程序代码实现需求变更,那么代码一定会在不断修改的过程中变得面目全非,这也意味着代码的腐坏。
有没有办法不修改代码却能实现需求变更呢?
这个要求听起来有点玄幻,事实上却是软件设计需要遵循的最基本的原则:开闭原则。

开闭原则

开闭原则说:软件实体(模块、类、函数等等)应该对扩展是开放的,对修改是关闭的
对扩展是开放的,意味着软件实体的行为是可扩展的,当需求变更的时候,可以对模块进行扩展,使其满足需求变更的要求。
对修改是关闭的,意味着当对软件实体进行扩展的时候,不需要改动当前的软件实体;不需要修改代码;对于已经完成的类文件不需要重新编辑;对于已经编译打包好的模块,不需要再重新编译。
通俗的说就是,软件功能可以扩展,但是软件实体不可以被修改
功能要扩展,软件又不能修改,似乎是自相矛盾的,怎样才能做到不修改代码和模块,却能实现需求变更呢?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了如何使用观察者模式和模板方法模式来实现软件设计的开闭原则。通过一个反面的例子展示了违反开闭原则的后果,以及如何使用策略模式和适配器模式来实现开闭原则。观察者模式和模板方法模式分别解决了一对多的对象依赖关系和按钮类型特有操作的问题,使得软件实体对扩展开放,对修改关闭。文章强调了抽象的重要性,指出实现开闭原则的关键在于抽象,并强调开闭原则是软件设计的核心原则。通过本文的讲解,读者可以了解如何利用设计模式来提高软件的灵活性和可扩展性,以及如何遵循开闭原则来指导和审视自己的设计。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《后端技术面试 38 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(43)

  • 最新
  • 精选
  • 山猫
    我同意老师通过这个例子简单的描述开闭原则。但如果项目初始就对button按钮需要进行这么复杂的设计,那么这个项目后期的维护成本也是相当之高。

    作者回复: 是否要使用各种设计模式设计一个非常灵活的程序,主要是看你的需求场景,而不是看项目的阶段。 如果你的场景就是需要这么灵活,就是要各种复用,应对各种变更,那么你一开始就应该这样设计。 如果你的场景根本不需要一个可复用的button,那么就不需要这样设计。 关键还是看场景。 但是场景也会变化,一开始不需要复用,但是后来又需要复用了,那么在在需要复用的第一个场景,就重构代码,而不是等将来维护困难局面hold不住了再重构。 ps 如果你习惯了这种灵活的设计,你会觉得这种设计并不复杂。对于软件开发而言,复杂的永远是业务逻辑,而不是设计模式。设计模式是可重复的,可重复的东西即使看起来复杂,熟悉了就会觉得很简单。 pps 看起来复杂的设计模式就是用来解决维护困难问题的,正确使用设计模式,看起来复杂了,其实维护简单了,因为关系和边界更清晰了,你不需要在一堆强耦合的代码里搅来搅去。真正维护成本高的其实是你所谓的简单的设计,牵一发动全身,稍不注意就是各种bug。 ppps 重要的话再说一次: 关键还是看场景。 没有银弹,没有一种必然就是好的设计方案,能理解场景的才是真·高手。

    2019-12-16
    3
    136
  • Paul Shan
    开闭原则是移除底层的if else,取而代之的是上层的类结构。不过,我个人以为一开始的if else, 甚至switch 也没什么不妥的,毕竟代码简单直接。引入了很多类,读代码也是负担,而且也很难预料到哪些修改是必要的。当if else数量多于一定的数目,再开始重构。 不知道李老师如何看待这种观点。

    作者回复: 当你准备写第一个else的时候,就说明你的代码即将陷入僵化、牢固和脆弱,而且为将来的需求变更引入了一个糟糕的“设计模式”。 如果其他人接手你的代码,他有两个选择,要么继续写更多的else以应对需求变更;要么心理暗骂一声然后重构你的代码。你希望他选择哪个?

    2019-12-16
    8
    8
  • 虢國技醬
    “开闭原则可以说是软件设计原则的原则,是软件设计的核心原则,其他的设计原则更偏向技术性,具有技术性的指导意义,而开闭原则是方向性的,在软件设计的过程中,应该时刻以开闭原则指导、审视自己的设计:当需求变更的时候,现在的设计能否不修改代码就可以实现功能的扩展?如果不是,那么就应该进一步使用其他的设计原则和设计模式去重新设计。” 读的过程中一直有这种感觉:开闭原则可能是软件设计和实现时最重要的原则;果然和老师最后的总结一样。👍

    作者回复: 👍

    2020-01-20
    4
  • Winon
    请教老师,模板方法是否也是另外一种面向过程设计?是否在充血对象模型中,模板方法的使用会相对少?

    作者回复: 模板方法常和策略模式结合,为各种策略实现类提供模板和公共处理逻辑。 充血模型也需要策略和模板。

    2020-07-05
    2
  • yes
    我不是想找茬,我就想知道以上的代码怎么对说好的加“*” 和“#” 开闭

    作者回复: starButton = new Button(); starButton.addListener( new ButtonListener() { public void buttonPressed() { dialer.enterDigit(STAR); } } );

    2020-01-27
    2
  • 唐二毛
    有一点想不通,在adapter里面还是需要判断呀?这并没有 达到老师说的 避免做switch/if判断的效果,而且判断的逻辑一点不少,还无端弄出这么多类,有必要非得这么做吗?

    作者回复: Adapter不需要判断,请看思考题

    2019-12-19
    1
  • whoami
    老师的风格像酒剑仙,我们目标做李逍遥,悟。

    作者回复: 谢谢~

    2021-04-09
  • 布拉姆
    老师,怎么理解“不要过早优化”这句话呢?在设计的早期就利用设计模式实现软件的“开闭原则”有什么关系呢

    作者回复: “不要过早优化”是说,为潜在的或即将到来的问题而优化;设计时要遵循“开闭原则”是说,需求变化一定会到来,功能一定会扩展,所以要遵循各种基本设计原则。

    2021-03-18
  • 席席
    李老师,我尝试用上面的设计模式套用到我之前写的1400行代码中,策略模式的确分开了最开始的if判断,分成了三个类。 但我在需要使用他们的时候,又不得不使用if判断代码是否属于这三个类中的某一个,我并没有消灭if条件,但是我感觉这么用挺好的! 还有我怀疑if真的能被消灭嘛?目前来讲,我只能做到拆分,无法消灭if

    作者回复: 用一个Factory返回策略类,把if的条件当做参数传给Factory,就不需要if了。 或者用一个map记录<if条件变量,策略对象>,从map中获得策略,也不需要if了。

    2020-09-18
    2
  • BestKF02
    有个问题: Button在优化到Adapter适配器的时候,token 怎么就不见了。没得token 怎么知道是取 DigitButtonDailerAdapter 还是 SendButtonDailerAdapter,这个片段突然缺失;老师有解答的地方吗?

    作者回复: 其实就是思考题的问题,建议参考代码思考一下

    2020-02-21
收起评论
显示
设置
留言
43
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部