- 设计模式是针对常见问题的通用解决方案,目前最为经典的设计模式有 23 种。
- 学习设计模式不仅能开拓思路、写出更优质的代码、提高项目的开发和维护效率;还能更好地阅读和理解源码,甚至可以根据文件名称直接推断出源码的架构设计。
介绍
你是否在项目开发中思考过如下几个问题?
- 同样都是写代码,为什么有些大佬的思路清晰、代码整洁,而我的代码却充满了重复和混乱,每次要修改时都无从下手、Bug 一堆?
- 如何写代码,才能使得项目易于扩展和维护?
- 我每天都在写重复的代码,如何提升水平?
- 为什么我读不懂大佬写的源码?是不是缺少了什么知识?
如果存在上述问题,那么我们一定要学习软件开发中的重要技能 —— 设计模式
!
设计模式是软件开发人员在软件开发过程中面临的一般问题的 通用 解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
通俗地说就是前辈们在写代码时摸索出了一些不错的方法,可以用于解决一类问题、更好地开发和维护项目。于是其他软件开发者纷纷效仿,久而久之,就得出了一套优秀的软件开发方法总结。
来源
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》(中文译名:《设计模式 - 可复用的面向对象软件元素》)的书,该书首次提到了软件开发中设计模式的概念。
知识
简要概述
《Design Patterns: Element of Reusable Object Oriented Software》
- Creational Patterns:
- Abstract Factory
- Builder
- Factory Method
- Prototype
- Singleton
- Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Behivioural Patterns
- Chain of Responsibility
- Command
- Interpreter
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
编程7大原则
- 单一指责原则 Single Responsibility Principle
- 开闭原则 Open-CLosed Principle
- 迪米特法则 Law of Demeter
- 依赖倒置原则 Dependency Inversion Principle
- 合成复用原则 Composite Reuse Principle
- 接口隔离原则 Interface Segregation Principle
- 里氏替换原则 Liskov Substitution Principle
设计模式分类
- 根据作用范围:类 / 对象模式
- 根据目的分类:创建型 / 结构型 / 行为型模式
创建型模式:如何创建对象
- 单例模式(懒汉式、饿汉式、双检锁、线程唯一单例)
- 工厂方法模式(类)
- 抽象工厂模式
- 建造者模式
- 原型模式
结构型模式:如何将类或对象结合在一起形成一个更强大的结构
- 适配器模式(类 / 对象)
- 组合模式
- 装饰器模式
- 代理模式
- 享元模式
- 外观模式
- 桥接模式
行为型模式:类或对象间如何交互、如何划分职责,从而更好地完成任务
- 迭代器模式
- 模板方法模式(类)
- 策略模式
- 命令模式
- 状态模式
- 责任链模式
- 备忘录模式
- 观察者模式
- 访问者模式
- 中介者模式
- 解释器模式(类)
知识图表
表一:GoF的23种设计模式一览表
创建型模式*5 Creational |
结构性模式*7 Structural |
行为型模式*11 Behavioral |
|
类模式 |
工厂方法模式 (Factory Method) |
(类)适配器模式 (Class Adapter) |
解释器模式 (Interpreter) 模板方法模式 (Template Method) |
对象模式 |
抽象工厂模式 (Abstract Factory) 建造者模式 (Builder) 原型模式 (Prototype) 单例模式 (Singleton) |
(对象)适配器模式 (Object Adapter) 桥接模式 (Bridge) 组合模式 (Composite) 装饰模式 (Decorator) 外观模式 (Facade) 享元模式 (Flyweight) 代理模式 (Proxy) |
职责链模式 (Chain of Responsibility) 命令模式 (Command) 迭代器模式 (Iterator) 中介者模式 (Mediator) 备忘录模式 (Memento) 观察者模式 (Observer) 状态模式 (State) 策略模式 (Strategy) 访问者模式 (Visitor) |
表二:GoF的23种设计模式简要说明
模式类别 | 模式名称 | 模式说明 |
---|---|---|
创建型模式*5 Creational Pattern |
抽象工厂模式 Abstract Factory Pattern |
提供一个创建一系列相关或者相互依赖的对象的接口,而无需指定它们具体的类 |
工厂方法模式 Factory Method Pattern |
定义一个用于创建对象的类型,但是让子类决定将哪一个类实例化;工厂方法模式让一个类的实例化延迟到子类 | |
建造者模式 Builder Pattern |
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 | |
原型模式 Prototype Pattern |
使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的类 | |
单例模式 Singleton Pattern |
确保一个类只有一个实例,并提供一个全局访问点来访问这个实例 | |
结构型模式*7 Structural Pattern |
适配器模式 Adapter Pattern |
将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 |
桥接模式 Bridge Pattern |
将抽象部分与它的实现部分解耦,使得两者都能够独立变化 | |
组合模式 Composite Pattern |
组合多个对象形成树形结构以表示具有部分-整体关系的层次结构,组合模式让客户端可以统一对待单个对象的组合对象 | |
装饰模式 Decorator Pattern |
动态的给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案; | |
外观模式 Facade Pattern |
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更容易使用 | |
享元模式 Flyweight Pattern |
运用共享技术有效地支持大量细粒度对象的复用 | |
代理模式 Proxy Pattern |
给某一个对象提供一个代理或占位符,并由代理对象来控制对原有对象的访问 | |
行为型模式*11 Behavioral Pattern |
职责链模式 Chain of Responsibility Pattern |
避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求;将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止 |
命令模式 Command Pattern |
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作 | |
迭代器模式 Iterator Pattern |
提供一种方法顺序访问一个聚合对象中的各个元素,而又不用暴露该对象的内部表示 | |
中介者模式 Mediator Pattern |
定义一个对象来封装一系列对象的交互;中介者模式使各对象之间不需要显式的相互引用,从而使其耦合松散,而且让你可以独立的改变他们之间的交互 | |
备忘录模式 Memento Pattern |
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态 | |
观察者模式 Observer Pattern |
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新 | |
状态模式 State Pattern |
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类 | |
策略模式 Strategy Pattern |
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法可以独立于使用它的客户变化 | |
模板方法模式 Template Method Pattern |
定义一个操作中算法的框架,而将一些步骤延迟到子类中;模板方法模式使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤 | |
访问者模式 Visitor Pattern |
表示一个作用于某对象结构中的各个元素的操作;访问者模式让你可以在不改变各个元素的类的前提下定义用于这些元素的新操作 |
表格来源: schips
表三:GoF的23种设计模式的对比
设计模式 | 适用层次 | 引入时机 | 复杂度 | 变化 | 实现 | 体现的原则 |
---|---|---|---|---|---|---|
工厂方法 | 代码级 | 编码时 | 简单 | 子类的实例化 | 对象的创建工作延迟到子类 | 开闭原则 |
单例 | 代码级、应用级 | 设计时、编码时 | 简单 | 唯一实例 | 封装对象产生的个数 | |
门面 | 应用级、构架级 | 设计时、编码时 | 简单 | 子系统的高层接口 | 封装子系统 | 开闭原则 |
模板方法 | 代码级 | 编码时、重构时 | 简单 | 算法子步骤的变化 | 封装算法结构 | 依赖倒置原则 |
抽象工厂 | 应用级 | 设计时 | 较复杂 | 产品家族的扩展 | 封装产品族系列内容的创建 | 开闭原则 |
组合 | 代码级 | 编码时、重构时 | 较复杂 | 复杂对象接口的统一 | 统一复杂对象的接口 | 里氏代换原则 |
代理 | 应用级、构架级 | 设计时、编码时 | 简单 | 对象访问的变化 | 封装对象的访问过程 | 里氏代换原则 |
命令 | 应用级 | 设计时、编码时 | 较简单 | 请求的变化 | 封装行为对对象 | 开闭原则 |
观察者 | 应用级、构架级 | 设计时、编码时 | 较简单 | 通讯对象的变化 | 封装对象通知 | 开闭原则 |
策略 | 应用级 | 设计时 | 一般 | 算法的变化 | 封装算法 | 里氏代换原则 |
建造者 | 代码级 | 编码时 | 一般 | 对象组建的变化 | 封装对象的组建过程 | 开闭原则 |
Adapter | 代码级 | 重构时 | 一般 | 对象接口的变化 | 接口的转换 | |
桥接 | 代码级 | 设计时、编码时 | 一般 | 对象的多维度变化 | 分离接口以及实现 | 开闭原则 |
装饰器 | 代码级 | 重构时 | 较复杂 | 对象的组合职责 | 在稳定接口上扩展 | 开闭原则 |
迭代器 | 代码级、应用级 | 编码时、重构时 | 较简单 | 对象内部集合的变化 | 封装对象内部集合的使用 | 单一职责原则 |
中介者 | 应用级、构架级 | 编码时、重构时 | 一般 | 对象交互的变化 | 封装对象间的交互 | 开闭原则 |
备忘录 | 代码级 | 编码时 | 较简单 | 状态的辅助保存 | 封装对象状态的变化 | 接口隔离原则 |
状态 | 应用级 | 设计时、编码时 | 一般 | 对象状态的变化 | 封装与状态相关的行为 | 单一职责原则 |
访问者 | 应用级 | 设计时 | 较复杂 | 对象操作变化 | 封装对象操作变化 | 开闭原则 |
原型 | 应用级 | 编码时、重构时 | 较简单 | 实例化的类 | 封装对原型的拷贝 | 依赖倒置原则 |
享元 | 代码级、应用级 | 设计时 | 一般 | 系统开销的优化 | 封装对象的获取 | |
责任链 | 应用级、构架级 | 设计时、编码时 | 较复杂 | 对象的请求过程 | 封装对象的责任范围 | |
解释器 | 应用级 | 设计时 | 较复杂 | 领域问题的变化 | 封装特定领域的变化 |
除了这 23 种主流设计模式外,还有一些其他设计模式,比如 Immutable 不可变模式等,了解即可。
学习建议
- 对设计模式的学习和其他知识一样,先了解每种设计模式是什么?作用是什么?能够解决什么问题?适用于什么场景?有什么特点?类和对象的关系是什么(建议结合 UML 类图来理解)?再去考虑编码实现和进一步在项目中应用。
- 一定要多写代码实践,最好每个设计模式都实现一遍,不要去背代码,用的多了自然就能写出代码了。
- 每个设计模式都可以 独立学习 ,互相之间联系不大,因此可以根据自己的时间来选择性学习(比如先学重点的单例模式)。
- 在学会基础的设计模式后,可以分析之前学过的框架源码(比如 Spring、MyBatis 等),参考别人是如何应用设计模式的。
- 不要过度依赖设计模式!!!!!! 它并不是银弹,过分使用设计模式可能只会增加系统的复杂度。
学习路线
主流的设计模式共有 23 种,建议大家按照以下四个阶段来学习:
- 基础学习
- 编码实现
- 项目实战
- 备战面试
其中第一个阶段和第二个阶段 可以同时进行 ,即对于每个设计模式的学习都是:先了解、再编码实现。
一、基础学习
本阶段的目标:依次了解每一种设计模式的应用场景、特点、UML 类图,能够对设计模式有个基础的印象。
学习顺序
根据使用频率、难易度、面试考察率等综合排序,仅供参考,并不绝对!
优先:
- 单例模式
- 工厂方法模式
- 迭代器模式
- 策略模式
- 建造者模式
- 模板方法模式
- 代理模式
- 责任链模式
- 抽象工厂模式
- 适配器模式
- 观察者模式
- 外观模式
一般:
- 桥接模式
- 组合模式
- 装饰器模式
- 状态模式
- 访问者模式
- 中介者模式
- 命令模式
- 备忘录模式
低优先:
- 原型模式
- 享元模式
- 解释器模式
推荐资源
以下资源看 1 - 2 个就足够入门了
- 书籍
- 《图解设计模式》:https://www.aliyundrive.com/s/jcQugLGNs1V 提取码: 5i9c(强烈推荐,用 Java 语言实现,图多、有示例代码、有习题和答案,很不错)
- 《大话设计模式》:https://www.aliyundrive.com/s/73jZWnfAtaA 提取码: 9gc7(比较有趣)
- 《Head First 设计模式》:https://www.aliyundrive.com/s/GnuQcruh7Us 提取码: 9gc7
- 《设计模式:可复用面向对象软件的基础》:https://www.aliyundrive.com/s/T9ECaPtxzg4 提取码: 9gc7(大黑书,难度较大,有能力和时间才去读)
- 《JavaScript 设计模式与开发实践》:https://www.aliyundrive.com/s/tzcZCU8bqnR 提取码: 9gc7(适合前端同学阅读)
- 《Python 设计模式》:https://www.aliyundrive.com/s/3RNoX31XqUy 提取码: 9gc7
- 视频
- 尚硅谷图解 Java 设计模式:https://www.bilibili.com/video/BV1G4411c7N4 (讲的很棒也很全面,也和一些主流框架相结合,系统学习 Java 的同学可以看)
- 五分钟学设计模式:https://www.bilibili.com/video/BV1af4y1y7sS (小短快科普,比较轻松)
- 黑马程序员Java设计模式详解:https://www.bilibili.com/video/BV1Np4y1z7BU (很完整,最后讲解了 Spring 框架的部分设计)
- 用一个项目讲解 23 种设计模式:https://www.bilibili.com/video/BV19g411N7yx (和项目结合,思路不错,但其中有一些直播翻车,可部分跳过)
- 文档
- 菜鸟教程:https://www.runoob.com/design-pattern/design-pattern-tutorial.html (还是比较推荐的,学过设计模式后如果忘记了,可以查看这个文档快速补回来)
- C++ 图说设计模式:https://design-patterns.readthedocs.io/zh_CN/latest/
- Go 语言设计模式系列博客:https://lailin.xyz/post/singleton.html
二、编码实现
本阶段的目标:依次编码实现每个设计模式,用任何支持面向对象的编程语言都可以,最好能够独立(不借助任何资料)从 0 写出每个设计模式的代码。
资源
一些源码示例,仅供参考,更多的内容可以直接在 GitHub 搜索关键词 Design Pattern
或 设计模式
:
- 各语言设计模式示例代码:https://github.com/wx-chevalier/design-pattern-examples
- Java 23 种设计模式全归纳:https://github.com/youlookwhat/DesignPattern (教程 + 源码)
- C++ 设计模式源码:https://github.com/liu-jianhao/Cpp-Design-Patterns (设计模式介绍 + 源码)
- JavaScript 示例代码:
- Python 示例代码:
- Go 示例代码
三、项目实战
本阶段的目标:通过做项目或阅读项目源码来进一步强化每个设计模式的实际应用。做到能根据某个场景主动选出合适的设计模式来优化代码、灵活运用,并且能够通过文件命名、项目目录结构等途径来快速判断出某个框架是否使用了设计模式。
可以先通过一个视频了解设计模式的实际应用:
- 实际工作中,如何运用 Java 设计模式:https://www.bilibili.com/video/BV1tK4y1s7Uo
列举一些设计模式在框架源码中的应用:
部分内容源于网络
- Spring
- 工厂模式:通过 BeanFactory 和 ApplicationContext 来创建对象
- 单例模式:Spring Bean 默认为单例模式
- 策略模式:例如 Resource 的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
- 代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术
- 模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate、JmsTemplate、JpaTemplate
- 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式
- 观察者模式:Spring 事件驱动模型
- 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
- Spring MVC
- 组合模式:WebMvcConfigurerComposite,树枝和树叶都实现了相同的抽象类或接口 WebMvcConfigurer
- 责任链模式:DispatcherServlet 依次拦截和处理请求
- 适配器模式:HandlerAdapter 处理器适配
- MyBatis(参考:https://blog.csdn.net/aha_jasper/article/details/108701785 )
- Builder + Factory 模式:创建 SqlSession 工厂和 SqlSession
- 模板方法模式:BaseExecutor 定义执行器基本流程
- 解释器模式:SqlNode 动态解析 SQL
- 单例模式:ErrorContext 线程唯一
- 装饰器模式:Cache 的实现用组合而非继承实现更灵活地缓存方式结合
- 迭代器模式:PropertyTokenizer 利用迭代器模式实现属性解析器
- 适配器模式:Log 适配不同的日志框架
- Google Guava(参考:https://blog.csdn.net/aha_jasper/article/details/108695561 )
- Builder 模式:更方便地构建内存缓存
- Wrapper 模式(代理模式、装饰器、适配器模式):轻松实现对类的扩展
- Immutable 模式:不可变集合实现,如 ImmutableList、ImmutableSet、ImmutableMap 等
- 更多可以自行学习:
- Netty
- SpringBoot
- Tomcat
- Dubbo
- Spring Cloud
四、备战面试
面试时对设计模式的考察主要有 4 种形式:
- 直接问你某个设计模式的作用和大致的原理,考察你对设计模式的了解程度
- 让你手写某个设计模式的代码,考察你对设计模式的熟悉程度和编码能力
- 给你一个实际的业务场景,让你去设计系统,考察你对设计模式的理解应用能力和逻辑思维
- 问你某个框架(轮子)的核心设计和源码细节,考察你对设计模式的理解应用能力
经典面试题
- 理论:简单介绍一下软件开发原则?
- 理论:设计模式如何分类?
- 你用过哪些设计模式?举例说明设计模式在你的项目或是某个框架源码中的应用。
- 说出某个设计模式的优缺点?什么时候使用它?
- 单例模式有哪些实现方式?分别有哪些优缺点?请手写其中一种
- 原型模式和单例模式的区别是什么?
- 简单工厂、工厂方法和抽象工厂三者有什么区别?
- 介绍一下代理模式,说一下静态代理和动态代理(比如 Spring AOP 就用到了)的区别?
资源
- 设计模式面试题汇总:https://pan.baidu.com/s/1tjIGc7pnHjgiFPo0fhcKXw, 提取码: wuan
- 面试官最爱问的13道”设计模式”题(视频):https://www.bilibili.com/video/BV1fR4y1N74H
- 设计模式图解与各语言实例代码 : https://refactoringguru.cn/design-patterns
- 图说设计模式,使用图形和C++代码结合的方式来解析设计模式 :https://design-patterns.readthedocs.io/zh-cn/latest/index.html