2021 年下半年的某个夜里,我接到同事打来的电话,他希望我帮忙解决一个技术问题。这个问题本身并不复杂,麻烦的是他在翻看底层源码时,被里面的复杂关系搞的晕头转向。虽然作者已经使用Decorator
结尾来表明相关类使用了装饰器模式,但显然我工作四年多的同事并不知道这一点。我问他之前有学习过设计模式吗?他告诉我之前尝试学习过,但书籍里的理论过于深奥,网上的资料又是抄来抄去,慢慢的就放弃了。这样的情况我已经遇到不止一次了,很多有工作经验的同事在面向对象设计能力上的欠缺让我有些惊讶,他们要么不愿意花时间丰富自己,要么就抱怨设计模式是那么隐晦、那么难以理解,以至于他们总是被拒之门外。
2022 年 3 月份,我一个在培训机构授课的朋友委托我写两份上课时使用的课件,分别关于 MySQL 和设计模式。钱很少,但我还是把活儿揽了下来,我想着课件是针对培训生的,所以可以比较简略。尽管这样我还是想得太乐观了,正如我的同事所说,网上有用的资料太少,光是设计模式的课件,我都花费两个月的时间(休息时间和周末)才整理好课件并交付。庆幸的是,在这之前我已经多次通读过《Design Patterns - Elements of Reusable Object-Oriented Software》一书,这也是我揽这份活儿的底气。
课件交付后,我决定对设计模式部分的内容进行完善,补充更多细节,并将其开源出来。
本项目主要探讨《Design Patterns - Elements of Reusable Object-Oriented Software》一书中提及的所有设计模式,结合我个人的理解和经验,对那些深奥的理论进行解析,并以实际的案例降低学习门槛。本项目主要由三部分内容组成:模式部分、总结部分和对比部分。模式部分涵盖了 23 种设计模式;总结部分主要包括有关于创建型模式的讨论、关于结构型模式的讨论和关于行为型模式的讨论;对比部分则是针对容易混淆的模式进行对比区分。对于单个模式主要从如下几个方面进行论述:
- 案例引入 => 引入实际案例,抛出在案例中遇到的问题;
- 问题分析 => 反复分析案例中的问题,循序渐进,逐步推导出解决方案;
- 解决方案 => 实现解决方案,包括解决方案的类图分析及代码附录等;
- 模式意图分析 => 正式进入该设计模式的内容,解释模式的意图定义;
- 模式通用结构 => 引入该模式的通用类图结构,对该模式中的每个参与者角色进行职责划分;
- 特点 => 罗列该模式的特点,如果需要,结合真实案例进行剖析;
- 适用场景 => 部分文章中对该模式的适用场景进行了阐述,为读者指明在哪些时候可以考虑使用该模式;
- 使用技巧 => 结合我个人的工作经验,部分文章中讨论了模式的使用技巧;
- 源码示例 => 列举源码中的示例,包括我们熟悉的 JDK、Spring、Mybatis 等;
- 扩展 => 其他相关的内容;
模式部分中的所有案例实现代码均使用的 Java 语言,如果你不熟悉 Java 语言,或许可以从类图结构中获益。
- 案例更真实 => 相较于部分网上资料来说,本项目中针对每种模式所引入的案例可能更加真实,有些甚至是我遇到过的实际场景;
- 见解更独到 => 不同于传统的阅读笔记,本项目中的每篇文档中的内容都掺杂了我个人现阶段的见解和体会;
- 体验更友好 => 本项目的绝大部分模式都是采用案例带出问题,进而分析问题,推导出解决方案,再进入模式的概念性内容。相较于一开篇就展开模式相关的内容来说,阅读体验感更好;
尽管在每种模式的文章中,我们将会通过一些案例来降低理解的难度,但这并不是说任何人都能无障碍的阅读文档,阅读该系列文章是需要一定的面向对象基础的。所以在此之前,你需要提前巩固一些面向对象基础,包括抽象、接口、封装、继承、多态、组合、聚合、线程安全等等。你也可以阅读面向对象七大原则相关内容,那有助于你更轻松的搞懂设计模式的目的。
只要你有一定的面向对象基础,每一种设计模式都并不复杂,设计模式并没有很神秘。但我们不得不承认,不管学什么,总有人喜欢以过来人的身份给你一些所谓的忠告。趁着他们还没有给你忠告,我先送你一条:远离那些试图阻止你学习的人,这很重要!
好读书,不求甚解;每有会意,便欣然忘食。 ——陶渊明《五柳先生传》
我比较推崇陶渊明先生的阅读方式,不纠结于一字一句的领会,才能不迷失在庞杂的知识海洋中。不同于其他知识,设计模式是一些前人积累的解决问题的设计经验,并不是一成不变的数学公式,所以大可不必较真于一个文章中不理解的句子。多阅读几次,或许你就领悟了。
或许你已经对某篇文章中的某个专业词汇感到困惑,那么这部分内容有可能帮助到你,因为这部分内容中包含了一些常用的专用名词的解释。另外,为了更清晰的描述类间关系,我们对每个模式都绘制了类图,但遗憾的是,目前市面上对于类图没有完全统一的标准。为了不引起歧义,我还在这部分内容中约定了一些规范,这些规范描述了文章中是按照何种原则在绘制类图。阅读这部分内容很有必要,它会让你在阅读时少走更多的弯路,更多内容-->名词解释及规范约定
本项目按照《Design Patterns - Elements of Reusable Object-Oriented Software》一书中的编目规则给所有模式进行了分类,主要包含三种:行为型模式、创建型模式和结构型模式。创建型模式强调对象的创建;结构型模式主要是在处理类(或者对象)之间的组合关系;行为型模式则是围绕着类(或者对象)之间如何交互、如何分配职责展开。除此之外,我们对每一个模式设定了一个 流行指数 和 难度等级,流行指数描述了模式在日常工作中的使用的频率,指数越高越流行;难度等级则描述了模式的复杂性。读者可根据自身情况自行选择阅读路线,例如如果你想系统的学习设计模式,推荐按照三种分类逐个击破。或者你只希望了解一些常用的模式,可按照流行指数从高到低进行。而对于面向对象基础较薄弱的读者来说,优先选择难度较低的模式更明智。
【B-1】责任链-Chain Of Responsibility
流行指数:★★★★☆ 难度等级:★★★★☆
意图简述:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
了解更多:如何支持可插拔的处理器组件?责任链模式能给你想要的答案
【B-2】命令-Command
流行指数:★★★☆☆ 难度等级:★★★★☆
意图简述:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
了解更多:命令模式 —— 优雅永不过时
【B-3】解释器-Interpreter
流行指数:★☆☆☆☆ 难度等级:★★★★★
意图简述:给定一种语言, 定义它的文法的一种表示 ,以及一个解释器,这个解释器使用这个表示来解释该语言中的句子。
了解更多:规则求解,首选解释器模式
【B-4】迭代器-Iterator
流行指数:★★★★★ 难度等级:★★☆☆☆
意图简述:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
了解更多:把遍历做到极致的迭代器模式
【B-5】中介者-Mediator
流行指数:★☆☆☆☆ 难度等级:★★★☆☆
意图简述:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
了解更多:中介者 —— 设计模式中的交际花
【B-6】备忘录-Memento
流行指数:★★★☆☆ 难度等级:★★☆☆☆
意图简述:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
了解更多:拥有记忆能力的备忘录模式
【B-7】观察者-Observer
流行指数:★★★★★ 难度等级:★★★☆☆
意图简述:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
了解更多:观察者模式 —— don't call us, we will call you
【B-8】状态-State
流行指数:★★★☆☆ 难度等级:★☆☆☆☆
意图简述:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
了解更多:如何用一句话总结状态模式?上坡加速,下坡减速
【B-9】策略-Strategy
流行指数:★★★★★ 难度等级:★☆☆☆☆
意图简述:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
了解更多:干掉 if-else?也许你真的误解策略模式了
【B-10】模板方法-Template Method
流行指数:★★★★★ 难度等级:★☆☆☆☆
意图简述:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
了解更多:从模板方法到钩子方法,简单又常用的模板方法模式
【B-11】访问者-Visitor
流行指数:★☆☆☆☆ 难度等级:★★★★★
意图简述:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
了解更多:探秘少有人知的访问者模式
【B-ending】关于行为型模式的讨论
从责任链模式开始,我们已经分别讨论了共计 11 个行为型的模式,他们分别是责任链(Chain Of Responsibility)、命令(Command)、解释器(Interpreter)、迭代器(Iterator)、中介者(Mediator)、备忘录(Memento)、观察者(Observer)、状态(State)、策略(Strategy)、模板方法(Template Method)和访问者(Visitor)模式。回顾所有的行为型模式,接下来我们将对他们进行横向对比,尝试探讨行为型模式的共性......更多内容-->关于行为型模式的讨论
【C-1】单例-Singleton
流行指数:★★★★★ 难度等级:★☆☆☆☆
意图简述:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
了解更多:面试高频题之手写单例类
【C-2】原型-Prototype
流行指数:★★★★☆ 难度等级:★★☆☆☆
意图简述:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
了解更多:从原型模式聊到深拷贝与浅拷贝
【C-3】建造者-Builder
流行指数:★★★★★ 难度等级:★★★☆☆
意图简述:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
了解更多:如何构建复杂对象?建造者模式了解一下
【C-4】工厂方法-Factory Method
流行指数:★★★★★ 难度等级:★★★☆☆
意图简述:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
了解更多:从静态工厂到工厂方法
【C-5】抽象工厂-Abstract Factory
流行指数:★★☆☆☆ 难度等级:★★★☆☆
意图简述:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
了解更多:最容易被名字劝退的设计模式 —— 抽象工厂
【C-ending】关于创建型模式的讨论
我们已经完整的介绍了 5 种创建型模式,他们分别是单例(Singleton)模式、原型(Prototype)模式、建造者(Builder)模式、工厂方法(Factory Method)模式和抽象工厂(Abstract Factory)模式。正如他们所属的分类名称一样,这几个模式都与对象的创建密切相关,但他们之间仍然有较大的区别,这些区别可能来自于类图结构、目的和所强调的侧重点等方面......更多内容-->关于创建型模式的讨论
【S-1】适配器-Adaptor
流行指数:★★★★★ 难度等级:★★☆☆☆
意图简述:将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
了解更多:简单又好用的适配器模式
【S-2】桥-Bridge
流行指数:★★☆☆☆ 难度等级:★★★☆☆
意图简述:当需要从多个维度对一个对象进行扩展时,我们可以使用桥模式来让各个维度分离,进而实现各自独立的变化...
了解更多:多个维度之间的解耦 —— 桥模式
【S-3】组合-Composite
流行指数:★★★★★ 难度等级:★★☆☆☆
意图简述:将对象组合成树形结构以表示“部分 - 整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
了解更多:树状结构的最佳实践 —— 组合模式
【S-4】装饰器-Decorator
流行指数:★★★★★ 难度等级:★★★☆☆
意图简述:在不改变原有对象结构的基础情况下,动态地给该对象增加一些额外功能的职责。
了解更多:不了解装饰器?想一想俄罗斯套娃
【S-5】门面-Facade
流行指数:★★☆☆☆ 难度等级:★☆☆☆☆
意图简述:为子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
了解更多:给系统增加门面,让客户使用起来更简单
【S-6】享元-Flyweight
流行指数:★★☆☆☆ 难度等级:★★★☆☆
意图简述:运用共享技术有效地支持大量细粒度的对象。
了解更多:利用池化思想来复用对象的享元模式
【S-7】代理-Proxy
流行指数:★★★★★ 难度等级:★★★☆☆
意图简述:为其他对象提供一种代理以控制对这个对象的访问。
了解更多:没有代理,就没有Spring-AOP
【S-ending】关于结构型模式的讨论
结构型模式共有 7 个,他们分别是适配器(Adapter)、桥(Bridge)、组合(Composite)、装饰器(Decorator)、门面(Facade)、享元(Flyweight)和代理(Proxy)模式。结构型模式重在描述如何将类或对象按照某种布局方式来组成更大的结构,相对于行为型模式来说,结构型模式更重要的是如何组织现有类或对象之间的关系,以此来满足我们所需的特性和功能......更多内容-->关于结构型模式的讨论
该系列文章推出后,收到了不少朋友的私信,其中有一些问题是关于如何鉴别两个设计模式的。为此,我整理了几组问的最多的模式区别,如果您对其他的模式有疑问,也可以私信我,或者在 issue 中留下您的问题。
(1)学无止境,温故知新
如果你已经学习过某一种模式,想知道更多关于该模式的内容,可以参阅设计模式相关的书籍,例如《Head First 设计模式》等(此处并不是推荐这本书,如有购书需求,请自行斟酌)。我相信书籍中的内容会比本项目更细致,更全面。
多看书,看好书。不管处于哪个阶段,温故知新总是没错的。拿《Design Patterns - Elements of Reusable Object-Oriented Software》来说,这本书不像小说,不能在阅读过一遍之后就束之高阁。这本书更适合反复阅读,反复理解,反复消化,因为每一次阅读我们都能有些不一样的感受,也能有些不一样的收获。
(2)不断实践,不断总结
对于大多数设计模式来说,唯一的提升途径就是:一边实践,一边总结。尝试在实践中使用设计模式,并不断的进行总结,总结引入设计模式后的优缺点、寻找是否还有更好的方案。(友情提示:如果你的项目隶属于公司,如果没有十足的把握和必要性,不要提交任何修改到远程仓库)
(3)不必为选择错误而感到担心
设计模式的最终目的是为了规划合理的结构和改善既有的代码,为了这样的目标我们借助于设计模式所提供的一些套路。但在面对实际需求时,最困难的部分是选择合适的模式(甚至是应不应该引入设计模式)。有时候我们发现可以套用一个设计模式(或几个模式的组合),又觉得另一个设计模式(或其他模式的组合)同样能套用在此处。而此时,就是你总结经验的最佳时机。 或许你会因为选择错误而付出代价,但在下次遇到类似问题的时候,你已经有失败过一次的经验了。不必为选择错误而感到担心,没有人是完美的,选错了大不了重来一次。
本项目中的文档均属原创,每一个字、每一行代码都是从休息时间挤出来的,文档中有错误或者不详尽的地方请谅解。发现错误可在 issue 中告诉作者,我将尽快处理;如果有问题或者疑惑的地方,欢迎在评论中留言讨论。
分享不易,如果觉得该文档帮到了你,star 是对作者的最大支持~~