设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17666 人已学习
课程目录
已更新 21 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (4讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

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

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

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

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(77)

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

    争哥的第一个例子,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
    2019-12-09
    12
  • 知行合一
    对原有代码改动越少,引入问题的概率越小。在熟悉业务的情况下,多思考一步,为了未来需求预留扩展点,就是有扩展性的代码。但凡事都有个度,扩展性和可读性需要不断权衡,需求在不断变化,系统也在不断演化,通过不断重构来解决过度设计的问题。
    2019-12-09
    7
  • Paul Shan
    基于一定的粒度(例如模块,类,属性等),扩展是平行地增加,修改是变更更细粒度的子集。扩展和修改和具体的粒度有关。不同的粒度下,扩展和修改定义不同。
    我个人以为,扩展的结果是引入了更多的平行结构(例如相似的派生类handler),以及支持这些平行结构的代码(利用多态,在关键的地方使用接口)。这些引入会让代码结构变的扁平一些,但是也更晦涩一些。修改,往往会增加代码的深度(这里指更低粒度的复杂度),例如,文中log例子,修改后,check函数有五个参数,内部的if else逻辑更多。但是,如果从参数以及if作用域的角度,这也可算作扩展。所以,扩展还是修改更本质的区别在于修改发生的粒度和层次。
    通常偏好修改发生在更高的层次上,这要求我们能够用接口和组合把系统合理的切分,做到高内聚和低耦合。高内聚可以让修改发生在更高层次上,替换掉整个低层次实现细节。低耦合,可以让模块之间的调用最小化,可以让高层次的修改最小化。
    支持高层次的平行结构不是免费的,除非有明确的收益(例如文中隔离Kafka实现细节的例子),不然还是让重构等待到需要的那一刻,预测未来的大部分平行结构其实不会被真正用到。
    2019-12-09
    6
  • (´田ω田`)
    修改老功能,可能需要重新进行各种功能验证、测试,并且如果是接收的遗留代码,更是费时费力;
    但是扩展的话,只需要对自己新增加的功能进行测试,工作量会小很多。
    2019-12-09
    6
  • 墨雨
    听前一部分的时候觉得,哇原来代码还可以这样重构,我以后写代码一定要这么写!看到最后,恩……还是要结合具体业务需求,考虑实现开闭的代价,取舍哪些需要适应开闭原则哪些可以忽略,避免过度设计。整体来说在写代码的时候要多思考一下如何应对短期内可能面临的变化。知识+经验+多思考,看起来编程没有银弹,多思考,多总结。
    2019-12-09
    4
  • 👽
    简单来说,就是尽量减少调用方为了应对而导致的变更。
    就例如本文的例子,为了应对变化需要增加函数的参数的时候。所有调用方都需要改代码。
    而如果依照开闭原则,则增加handler 以及相应修改即可。并不会影响调用方。

    其实个人认为,也是通过了 类似于“中间件”的形式。例如,小明,作为公司代表需要跟各个国外公司的人谈业务。他去跟美国人谈业务,需要学英语;跟日本谈业务,要学日语;跟毛子谈业务,又要学毛子语。
    这时候,的解决方案:
    1,跟各个国家说好,大家都说英语。或者都说汉语。就算再有其他的国家,也让他强制用英语。
    2,小明自己只用汉语。然后谈业务时,带个多语种翻译,去谈业务时把翻译带上。这时候,如果有新的国家需要新的语种,那么就让翻译去掌握更多的语种。
    应对今天的例子,翻译掌握的语种,其实就是handler。小明和各国代表谈业务时,各自都不需要变更自己的接口。只需要对【翻译】进行扩展即可。
    2019-12-10
    2
  • 辉仔lovers
    老师 您好,请教几个问题
    AlertHandler 使用的是抽象类,而不是接口。就是为了让子类去继承构造方法吗?
    这个扩展跟spring中handlerMapping的写法一样,类似于策略模式吧?
    单例模式的时候 使用静态代码块来初始化添加handler 随着类加载一次是不是就不用搞成单例的了?
     static{
            alertRule = new AlertRule(/*.省略参数.*/); //省略一些初始化代码
            notification = new Notification(/*.省略参数.*/); //省略一些初始化代码
             alert = new Alert(alertRule,notification);
             alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
             alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
        }

    我们是不是可以把实现类(不同的handler)放到配置文件中。使用jdk的spi扩展机制。更加灵活一些?
    2019-12-09
    2
  • 李小四
    设计模式_16
    # 作业:
    开闭原则核心好处是:
    - 减少因为新增功能而增加的工作量
    - 减少因为新增功能而增加的出错数

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

    需求变更对于代码结构影响很大时,要提高对其扩展的权重;读到这里时,我拍了一下大腿,我想,我更加理解开闭原则了。
    2019-12-09
    2
  • deepz
    老师您好,我把代码实践了后发现, 单例初始化那块可能有点问题。private static final ApplicationContext instance = new ApplicationContext();

        private ApplicationContext() {
            instance.initializeBeans();
        }
    这个“instance”报了空指针。
    2019-12-10
    3
    1
  • wenxueliu
    spring 是如何应用开闭原则的,可以参考本文https://blog.csdn.net/wenxueliu/article/details/103467359
    2019-12-10
    1
  • 土豆哪里挖
    什么时候出其他语言的demo呢,不懂java,理解起来太痛苦了

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

    2019-12-09
    1
  • L.
    学到了,谢谢老师;
    2019-12-09
    1
  • 编程界的小学生
    个人分析的原因
    1.不这么做的话将来某个方法会越积越多,代码行数越来越长,等回过头来修改的时候自己可能都要读上十分钟才能找到下手的机会。
    2.确实有好处,每次只修改修改具体实现,而对外是无感知的,调用方并不关心实现细节。
    3.需求下来后,分析出某块东西可能会频繁更改后,且采取了这种对修改关闭,对扩展开放的思想来编码后,那以后需求变动的时候才知道什么叫真香。
    2019-12-09
    1
  • 小晏子
    对于课后题,想到2点:
    1,减少出错概率,修改出错的概率比扩展要大
    2,边界的问题,比如用户边界,尽量减少用户侧代码的改动,比如文中alert的事例,check函数本身的修改意味着所有使用的地方都要修改,而使用了开闭原则的代码对于老用户是无须修改的,降低了用户修改的成本。
    2019-12-09
    1
  • 葫芦娃
    例子中的AlertRule类也需要修改,添加MaxTimeout定义。可以考虑把规则按统一的格式写入数据库,统一解析,这样扩展规则只要添加规则数据就行了,不用改代码
    2019-12-09
    3
    1
  • 黄林晴
    打卡✔
    2019-12-09
    1
  • 小刀
    抽象类与接口实现封闭,继承与实现 实现了扩展
    开闭原则
    2019-12-14
  • 用0和1改变自己
    对拓展开放是为了应对变化(新的需求),方便添加删除,对修改关闭是为了保证已有代码都稳定性(防止修改一个需求引出千万个bug),最终还是为了系统更有弹性,更易维护
    2019-12-14
  • javaadu
    课堂作业:
    写代码遵循开闭原则的目的是为了新增代码的时候尽量不要影响原来的核心逻辑,同时可以灵活应对需求的变化
    2019-12-13
收起评论
77
返回
顶部