- 贯穿设计模式:用一个电商项目详解设计模式
- 伟山育琪
- 1429字
- 2024-12-28 11:44:56
1.2.6 开闭原则
开闭原则中,“开”是指对扩展开放,“闭”是指对修改关闭。简单来说,开闭原则就是指:如果你想要修改一个功能,请不要直接进行内部的代码修改,而是使用扩展的方式进行。如果读者对此原则感觉模糊,也不必着急,在我们后续项目实战章节“日志的解析处理”部分,会通过“模板方法模式”进行项目的实战落地,而模板方法模式就是开闭原则的典型设计模式之一。
按照笔者的习惯,我们依然要以源码为示例,对开闭原则的应用示例进行说明。开闭原则的源码应用非常广泛,我们可以基于上节内容涉及的Spring容器核心refresh()方法中的后置处理器扩展方法postProcessBeanFactory(bean Factory)进行说明,想要实现不同功能的后置处理器,不需要修改Spring本身的代码(对内修改关闭),只需要子类覆写postProcessBeanFactory(beanFac tory)方法进行后置处理器的定义即可(对外扩展开放);也可以基于refresh()方法中的onRefresh()方法进行说明,引入SpringBoot内嵌Web容器的源码实现,对SpringBoot源码中的ServletWebServerApplicationContext类进行分析,体会Spring对SpringBoot的扩展开放;还可以基于JDK的HashMap源码为其子类LinkedHashMap提供的afterNodeAccess、afterNodeInsertion、afterNodeRemoval三大扩展方法进行说明,体会HashMap的对内修改关闭,以及对外(LinkedHashMap)开放扩展的设计等。
但是,经过笔者的再三考虑,我想为大家提供更加热门的、有关并发编程的AQS源码示例来阐述开闭原则的“对外扩展开放”与“对内修改关闭”。
相信大家对AQS并不陌生,它的全称是AbstractQueuedSynchronizer,它是位于JDK源码Java并发包中的一个抽象类,此类基于FIFO(First In - First Out,先进先出)队列,提供了实现锁和线程同步的框架,我们开发过程中经常使用到的ReentrantLock就是基于AQS抽象类进行的锁的实现。
前文提到过模板方法模式是开闭原则的代表性实现,AQS也是基于模板方法模式的设计,我们先来看看AQS中“对外扩展开放”的源码片段,代码如下:
我们可以看到,AQS源码中提供了五个需要扩展的方法,这些方法都是同步锁最核心的逻辑:加锁、释放锁、是否持有锁判断。如此核心的逻辑可以任由子类进行个性化扩展,这就是所谓的“对外开放扩展”。
那么,“对内修改关闭”从何处体现呢?AQS将以上五个方法,嵌入到了统一的代码模板中,虽然支持子类个性化的扩展核心逻辑,但是整体执行流程是不可以进行修改的,为了说明“对内修改关闭”,我们先来看以下源码片段。
从以上代码我们可以清晰地看到,tryAcquire方法的调用时机和tryRelease方法的调用时机,都已经被AQS实现了,AQS对整体的调用逻辑和时机进行了全面的把控,子类无法进行修改,这就是“对内修改关闭”的写照。
笔者经常把AQS开闭原则的设计比喻成生老病死的自然规律,芸芸众生,从呱呱坠地到归于尘土,这一自然规律无法修改,这就是“对内修改关闭”;然而,众生万象,都有各自的轨迹,不同的选择造就了不同的命运轨迹,这就是“对外扩展开放”。无论子类如何扩展,终究难免归于尘土。
我们继续补充笔者总结的那句话:“单一职责原则以职责为基准划分类和接口;划分出来的接口需要最小化,剔除无用接口方法,在接口隔离原则下进行精确的使用;子类对父类的实现需要依据里氏替换原则,在实现所有抽象方法的前提下,可以增加个性化功能,至此子类和父类就创建完成了;当我们使用该类时,要面向接口编程,遵循依赖倒置原则;不同的类或接口之间,要遵循最小知识原则,减少互相的耦合,遵循迪米特法则;如果你的设计需要修改,请尽量提升代码的扩展性,追求对内修改关闭,对外开放扩展的开闭原则。”
设计模式的原则,固然是无数先辈程序员留给我们最好的财富,我们会认真品味、吸收,融会贯通到实际的开发场景之中。但规则是死的,人是活的,我们依然要以具体的业务流程和需求场景为出发点进行代码的设计,因地制宜,没有最优的,只有最合适的。