• 「设计模式」六大原则之开闭职责小结



    「设计模式」六大原则系列链接:
    「设计模式」六大原则之一:单一职责小结
    「设计模式」六大原则之二:开闭职责小结
    「设计模式」六大原则之三:里氏替换原则小结
    「设计模式」六大原则之四:接口隔离原则小结
    「设计模式」六大原则之五:依赖倒置原则小结
    「设计模式」六大原则之六:最小知识原则小结

    六大原则体现很多编程的底层逻辑:高内聚、低耦合、面向接口编程、面向抽象编程,最终实现可读、可复用、可维护性。

    设计模式的六大原则有:

    • Single Responsibility Principle:单一职责原则
    • Open Closed Principle:开闭原则
    • Liskov Substitution Principle:里氏替换原则
    • Law of Demeter:迪米特法则(最少知道原则)
    • Interface Segregation Principle:接口隔离原则
    • Dependence Inversion Principle:依赖倒置原则

    把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。

    在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。

    本文介绍 SOLID 中的第二个原则:开闭原则

    1.开闭原则定义

    开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

    添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

    代码的扩展性是代码质量评判的最重要的标准之一。 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。

    开闭原则的核心思想:面向抽象编程。

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

    添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。

    关于定义,我们有两点要注意。

    第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。

    第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

    3. 如何做到“对扩展开放、修改关闭”?

    我们要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,我们要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。

    在识别出代码可变部分和不可变部分之后,我们要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。

    在众多的设计原则、思想、模式中,最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)。

    实际上,多态、依赖注入、基于接口而非实现编程,以及前面提到的抽象意识,说的都是同一种设计思路,只是从不同的角度、不同的层面来阐述而已。这也体现了“很多设计原则、思想、模式都是相通的”这一思想。

    4. 如何在项目中灵活应用开闭原则?

    有一句话说得好,“唯一不变的只有变化本身”。

    即便我们对业务、对系统有足够的了解,那也不可能识别出所有的扩展点,即便你能识别出所有的扩展点,为这些地方都预留扩展点,这样做的成本也是不可接受的。我们没必要为一些遥远的、不一定发生的需求去提前买单,做过度设计。

    我们需要在可读性和可维护性做一个平衡。

    5. 示例:

    举例说明如何用继承体现 开闭原则的。

    以某个付费课程为例,首先定义一个课程接口:

    public interface ICourse {
        Integer getId();
        String getName();
        Double getPrice();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    课程有Java、Web、Android、iOS等。这里以 Android 课程为例:

    public class AndroidCourse implements ICourse{
        private Integer Id;
        private String name;
        private Double price;
        public AndroidCourse(Integer id, String name, Double price) {
            this.Id = id;
            this.name = name;
            this.price = price;
        }
       public Double getPrice() {
            return this.price;
        }
       // 其他 set和 get 省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    新需求来了,双十一要做活动,Android 课程 要打 7 折促销。

    如果直接以之前的类 AndroidCourse ,会引入风险,我们如何在不修改原有代码的前提前下,实现价格优惠这个功能呢?

    根据开闭原则的指导思想,我们再写一个处理优惠逻辑的类 AndroidDiscountCourse

    public class AndroidDiscountCourse extends AndroidCourse {
        public JavaDiscountCourse(Integer id, String name, Double price) {
            super(id, name, price);
        }
        public Double getOriginPrice(){
            return super.getPrice();
        }
        public Double getPrice(){
            return super.getPrice() * 0.7;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过继承父类扩展需要的方法,同时可以保留原有的方法,新增自己的方法。

    6. 小结:

    对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!

    开闭原则:基于接口或抽象实现“封闭”,基于实现接口或继承实现“开放”(拓展)。

    参考:

    《设计模式之美》

    《重学 Java 设计模式》

  • 相关阅读:
    2023华为杯研究生数学建模F题思路分析
    Android 生成并分享海报,到微信
    如何实现制造业信息化转型?
    体重秤智能电子秤方案开发设计
    π162E60 Pai162E60 5.0kVrms 200Mbps 六通道数字隔离芯片代替Si8662BD-B-IS
    华为开发者大会2022,发布鸿蒙开发套件
    睿趣科技:抖音开通蓝V怎么操作的
    Microsemi Libero SoC 教程2 (点亮LED闪烁)
    基于单片机的自动停车收费系统的设计
    [附源码]Python计算机毕业设计SSM开放式在线课程教学与辅助平台(程序+LW)
  • 原文地址:https://blog.csdn.net/jun5753/article/details/126861992