16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?

2019-12-09 王争
《设计模式之美》
课程介绍


讲述:冯永吉

时长:大小17.33M


在上一节课中,我们学习了单一职责原则。今天,我们来学习 SOLID 中的第二个原则:开闭原则。我个人觉得,开闭原则是 SOLID 中最难理解、最难掌握,同时也是最有用的一条原则。
之所以说这条原则难理解,那是因为,“怎样的代码改动才被定义为‘扩展’?怎样的代码改动才被定义为‘修改’?怎么才算满足或违反‘开闭原则’?修改代码就一定意味着违反‘开闭原则’吗?”等等这些问题,都比较难理解。
之所以说这条原则难掌握,那是因为,“如何做到‘对扩展开放、修改关闭’?如何在项目中灵活地应用‘开闭原则’,以避免在追求扩展性的同时影响到代码的可读性?”等等这些问题,都比较难掌握。
之所以说这条原则最有用,那是因为,扩展性是代码质量最重要的衡量标准之一。在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。
所以说,今天的内容非常重要,希望你能集中精力,跟上我的思路,将开闭原则理解透彻,这样才能更好地理解后面章节的内容。话不多说,让我们正式开始今天的学习吧!

如何理解“对扩展开放、修改关闭”...


展开全文
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。

精选留言

  • 下雨天
    2019-12-09
    对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!
    15
    525
  • 辣么大
    2019-12-09
    开闭原则:基于接口或抽象实现“封闭”,基于实现接口或继承实现“开放”(拓展)。

    争哥的第一个例子,AlertHandler为抽象,一般是固定不变的。子类TpsAlertHandler为继承;再看第二个例子,MessageQueue,MessageFormater为接口,具体实现为KafkaMessageQueue和JsonMessageFromatter等。以后替换或者增加其他的AlertHandler和message queue很容易。

    两个例子中的抽象类和接口是固定的(封闭),继承或实现是可扩展的。通过“抽象-具体”体现了开闭原则,增加了软件的可维护性。

    开闭原则具体应用,需要慢慢积累经验。争哥也说了,首先需要有对业务深刻的理解。其次就是学习一些设计原则和模式了。

    补充:
    1、Bertrand Meyer 1988 年提出open-closed principle。
    2、再推荐一篇经典文章 Robert C. Martin 2006年写的The Open-Closed Principle。不方便下载的话,我放到github上了:https://github.com/gdhucoder/Algorithms4/tree/master/designpattern/pdf
    展开
    5
    89
  • Paul Shan
    2019-12-09
    基于一定的粒度(例如模块,类,属性等),扩展是平行地增加,修改是变更更细粒度的子集。扩展和修改和具体的粒度有关。不同的粒度下,扩展和修改定义不同。
    我个人以为,扩展的结果是引入了更多的平行结构(例如相似的派生类handler),以及支持这些平行结构的代码(利用多态,在关键的地方使用接口)。这些引入会让代码结构变的扁平一些,但是也更晦涩一些。修改,往往会增加代码的深度(这里指更低粒度的复杂度),例如,文中log例子,修改后,check函数有五个参数,内部的if else逻辑更多。但是,如果从参数以及if作用域的角度,这也可算作扩展。所以,扩展还是修改更本质的区别在于修改发生的粒度和层次。
    通常偏好修改发生在更高的层次上,这要求我们能够用接口和组合把系统合理的切分,做到高内聚和低耦合。高内聚可以让修改发生在更高层次上,替换掉整个低层次实现细节。低耦合,可以让模块之间的调用最小化,可以让高层次的修改最小化。
    支持高层次的平行结构不是免费的,除非有明确的收益(例如文中隔离Kafka实现细节的例子),不然还是让重构等待到需要的那一刻,预测未来的大部分平行结构其实不会被真正用到。
    展开
    
    30
  • 何妨
    2019-12-09
    听前一部分的时候觉得,哇原来代码还可以这样重构,我以后写代码一定要这么写!看到最后,恩……还是要结合具体业务需求,考虑实现开闭的代价,取舍哪些需要适应开闭原则哪些可以忽略,避免过度设计。整体来说在写代码的时候要多思考一下如何应对短期内可能面临的变化。知识+经验+多思考,看起来编程没有银弹,多思考,多总结。
    1
    29
  • 古杨
    2019-12-26
    我所在的公司,现在写代码入参全用map,写了两年我都不知道什么叫对象了。感觉自己废了☹️
    15
    24
  • (´田ω田`)
    2019-12-09
    修改老功能,可能需要重新进行各种功能验证、测试,并且如果是接收的遗留代码,更是费时费力;
    但是扩展的话,只需要对自己新增加的功能进行测试,工作量会小很多。
    
    19
  • 知行合一
    2019-12-09
    对原有代码改动越少,引入问题的概率越小。在熟悉业务的情况下,多思考一步,为了未来需求预留扩展点,就是有扩展性的代码。但凡事都有个度,扩展性和可读性需要不断权衡,需求在不断变化,系统也在不断演化,通过不断重构来解决过度设计的问题。
    
    14
  • 木木
    2019-12-11
    文章写的是真的好,很容易读懂。主要的还是要知道为什么要这么做。感谢老师。
    
    12
  • feifei
    2019-12-18
    文中的alter 一步一步的改造,看的眼花缭乱的😂,我就问下,为什么不能直接在原始的Alter 类中,重载一个只有新增业务参数的check 放到的,这样不就最简单,原先开发好也不用动,这样对于Alter 类来说不是对扩展开放,对修改关爱了吗?请教下大神,我这种用重载的思路有啥,不好的地方

    作者回复: 重载可以。但如果报警规则很多的花 类会无限膨胀 可读性比较茶

    5
    11
  • CoderJ
    2020-03-28
    计算机技术就是一门权衡的技术!
    2
    8
  • deepz
    2019-12-10
    老师您好,我把代码实践了后发现, 单例初始化那块可能有点问题。private static final ApplicationContext instance = new ApplicationContext();

        private ApplicationContext() {
            instance.initializeBeans();
        }
    这个“instance”报了空指针。
    展开
    7
    8
  • 李小四
    2019-12-09
    设计模式_16
    # 作业:
    开闭原则核心好处是:
    - 减少因为新增功能而增加的工作量
    - 减少因为新增功能而增加的出错数

    # 感想:
    之前一直有一些执念,想要找到某一原则非黑即白的分割线。比如开闭原则,有两个极端:
    - 任何的“修改”都不能接受
    - 任何不能“扩展”的代码都不能接受
    然后就进入了“走火入魔”的状态,最终陷入对原则的怀疑。

    需求变更对于代码结构影响很大时,要提高对其扩展的权重;读到这里时,我拍了一下大腿,我想,我更加理解开闭原则了。
    展开
    
    8
  • 梦倚栏杆
    2019-12-09
    关于修改后的报警规则代码实现有两个疑问:
    1. ApiStateInfo class 是充血模型还是贫血模型。
    2.其实各个handler侧重的是不同的方方面面,比如错误次数,超时次数。统一接收ApiStateInfo 和 某一个handler接收具体的类比如:ErrorRequestApiStateInfo, TimeOutStateInfo, 哪种方式好呢?比较依据是什么

    作者回复: 1. 是贫血模型
    2. 不好讲,拆分之后,类增加,维护成本高一些,但职责更单一,更加高内聚、低耦合,扩展性更好些。

    2
    8
  • 👽
    2019-12-10
    简单来说,就是尽量减少调用方为了应对而导致的变更。
    就例如本文的例子,为了应对变化需要增加函数的参数的时候。所有调用方都需要改代码。
    而如果依照开闭原则,则增加handler 以及相应修改即可。并不会影响调用方。

    其实个人认为,也是通过了 类似于“中间件”的形式。例如,小明,作为公司代表需要跟各个国外公司的人谈业务。他去跟美国人谈业务,需要学英语;跟日本谈业务,要学日语;跟毛子谈业务,又要学毛子语。
    这时候,的解决方案:
    1,跟各个国家说好,大家都说英语。或者都说汉语。就算再有其他的国家,也让他强制用英语。
    2,小明自己只用汉语。然后谈业务时,带个多语种翻译,去谈业务时把翻译带上。这时候,如果有新的国家需要新的语种,那么就让翻译去掌握更多的语种。
    应对今天的例子,翻译掌握的语种,其实就是handler。小明和各国代表谈业务时,各自都不需要变更自己的接口。只需要对【翻译】进行扩展即可。
    展开
    2
    7
  • 哈喽沃德
    2019-12-10
    我想这篇对扩展开放,多修改关闭的文章应该会成为争哥这个设计模式系列最好的文章。很难想象,一个杰出的程序员的语言思维逻辑也是如此清晰。
    
    5
  • 土豆哪里挖
    2019-12-09
    什么时候出其他语言的demo呢,不懂java,理解起来太痛苦了

    作者回复: 关注我的github:https://github.com/wangzheng0822

    2
    5
  • ub8
    2020-12-28
    个人认为可读性是第一位的,代码写的再NB,一个组的只有你自己能看懂有什么用呢?
    1
    4
  • wenxueliu
    2019-12-10
    spring 是如何应用开闭原则的,可以参考本文https://blog.csdn.net/wenxueliu/article/details/103467359
    2
    4
  • 业余爱好者
    2019-12-09
    扩展指的是“修改”或替换一个系统的功能,修改指的是对系统整体结构的篡改。系统的整体架构是不应该有大变动的,它相当于系统的本质,是相对稳定的部分。如果乱动的话,系统会变得连妈都不认识。

    开闭原则不仅用于软件开发,拿计算机硬件设计为例。计算机的本质在于计算,这是计算机稳定的部分,是不应该乱动的。对应与计算机中的硬件就是CPU,CPU的指令集可以说相当稳定,几十年来几乎只是从16位变成32位,64位。(这里是不是也体现了开闭?当然这不是重点)。而io的部分是异变的,磁盘,显示器,鼠标,打印机,不一而足。稳定的是什么,是输入输出(相当于没说,本来就是io嘛)。对于计算机来说,它不管你使用什么io设备。在我CPU看来,就是在执行一条io指令(指令本身是稳定的),具体io逻辑的实现交给各种设备控制器。妥妥的开闭原则。
    展开
    
    3
  • 航哥很帅
    2020-11-17
    之所以会有对扩展开放,对修改关闭的原则,是因为对扩展开放能够应对业务需求的变化,从而实现已有功能的扩展,而对修改关闭是为了保证在扩展新的需求是,能够保证已有功能的稳定。


    对扩展开放,对修改关闭原则,听起来很简单,就是指我们在开发时尽可能的少修改已有的代码,而应该增加新的代码来实现新的功能。但对于这个原则,往往是很难绝对执行的,因为即使是完全增加新的功能,也很难做到百分百不修改原来的代码。所以,对扩展开放,对修改关闭的原则,用大白话来说就是在尽可能少修改以后功能代码的情况下,通过增加新的功能代码来实现新的功能。


    如果想做到对扩展开放,对修改关闭,作为一个程序员要有扩展意识、封装意识和抽象意识。这三个意识听起来很简单,但要真正的做到必须要多实践多练习才能够慢慢的心领神会。
    展开

    作者回复: 总结的好

    
    2