设计模式复习总图:七大原则与二十三种模式完整知识骨架
简介:这份《大话设计模式复习》文档面向具备一定面向对象编程基础、工作1至3年的研发人员,系统梳理设计模式的核心知识体系。内容从设计模式的定义、分类、特点与应用场景切入,详细讲解单一职责、开放-封闭、依赖倒转、里氏代换、迪米特法则、合成/聚合及接口隔离七大设计原则,并深入剖析抽象工厂、策略、外观、适配器、组合、单例、中介者七种常用模式,以及其余十五种模式的UML类图、基础代码与实例。文档还横向对比不同模式的差异,总结模式选择的关键维度,并附练习题与模拟试卷辅助巩固。资源包为单个docx文件,约3.66MB,结构完整、目录清晰,便于按章节检索学习。目前已有191人学习,适合希望提升代码可维护性、可扩展性与复用性,并在实际项目中灵活选用设计模式的开发者参考。
1. 设计模式复习总图:从七大原则到二十三种模式的完整知识骨架
设计模式这块内容,最怕的不是记不住,而是脑子里没有一张总图。很多人复习时翻开资料就从单例模式开始背,背到代理模式已经忘了抽象工厂在讲什么,最后脑子里只剩一堆零散的类图。真正有效的复习路径是先建立分类坐标系:创建型管对象怎么生,结构型管类与对象怎么拼,行为型管对象之间怎么通信与分责。三大类共二十三种经典模式,加上七大设计原则作为底层判据,再配合易混淆模式对比表,才构成一个可检索、可推导的复习体系。这份笔记梳理的就是这样一条从原则到模式、从单模式拆解到跨模式对比的完整链路,适合已经写过面向对象代码、正在系统复习或准备面试的人拿来当查漏补缺的地图用。
2. 七大设计原则:模式背后的判据与易混边界
2.1 七条原则各自管什么
设计原则不是拿来背的,是拿来判断“这个模式为什么长这样”的。梳理下来会发现,二十三种模式几乎都能回溯到某几条原则的约束。
| 原则 | 一句话核心 | 典型落地模式 |
|---|---|---|
| 单一职责 SRP | 一个类只有一个引起变化的原因 | 计算器运算类拆分 |
| 开放-封闭 OCP | 对扩展开放,对修改关闭 | 策略模式新增算法 |
| 依赖倒转 DIP | 高低层都依赖抽象,细节依赖抽象 | 工厂模式依赖产品接口 |
| 里氏代换 LSP | 子类必须能替换父类且行为契约不变 | 正方形与矩形的经典反例 |
| 迪米特法则 LOD | 不和陌生人说话,通过第三者转发 | 中介者模式 |
| 合成/聚合 CARP | 优先组合而非继承 | 装饰模式 |
| 接口隔离 ISP | 用多个专门接口代替一个总接口 | 动物接口拆分为飞行、陆地 |
这张表建议反复看,因为后面每一种模式的分析都会回到这里。比如抽象工厂模式,它同时踩中依赖倒转和合成/聚合,但在新增产品等级结构时会违反开放-封闭——这就是原则之间的张力,也是考试和面试最爱问的地方。
2.2 最容易搞混的三组原则
依赖倒转 vs 里氏代换。依赖倒转讲的是“依赖谁”——依赖抽象不依赖具体;里氏代换讲的是“替换后还行不行”——子类替换父类后行为不能变。一个管方向,一个管契约。
迪米特法则 vs 接口隔离。迪米特管的是“少跟不相关的类直接通信”,接口隔离管的是“别让类依赖它用不到的方法”。前者是通信路径问题,后者是接口粒度问题。
合成/聚合 vs 开放-封闭。合成/聚合是实现开放-封闭的手段之一——通过组合而非继承来扩展功能,装饰模式就是典型。但开放-封闭还可以通过策略、工厂等模式实现,所以两者不是一回事。
提示:复习原则时不要孤立背定义,每一条都找一个具体模式作为锚点,回忆时先想模式再反推原则,比正着背快得多。
3. 七大重点模式拆解:抽象工厂、策略、外观、适配器、组合、单例、中介者
3.1 抽象工厂模式:产品族的创建接口
抽象工厂的核心是“产品族”概念。所谓产品族,是指位于不同产品等级结构、功能相关联的产品组成的家族。比如数据库访问组件中,User 表和 Department 表就是两个产品等级结构,而 SqlServer 系列和 Access 系列就是两个产品族。
角色分工:
- 抽象工厂 AbstractFactory:声明创建产品族的方法,如 createProductA()、createProductB()
- 具体工厂 ConcreteFactory:实现工厂方法,创建具体产品族
- 抽象产品 AbstractProduct:定义产品接口
- 具体产品 ConcreteProduct:实现抽象产品接口,内部包含业务逻辑
// 抽象工厂接口:声明创建两个产品等级结构的方法 public interface AbstractFactory { AbstractProductA createProductA(); AbstractProductB createProductB(); } // 具体工厂1:创建产品族1 public class ConcreteFactory1 implements AbstractFactory { public AbstractProductA createProductA() { return new ProductA1(); } public AbstractProductB createProductB() { return new ProductB1(); } } // 客户端:只依赖抽象工厂和抽象产品 AbstractFactory factory1 = new ConcreteFactory1(); AbstractProductA a1 = factory1.createProductA(); AbstractProductB b1 = factory1.createProductB();参数说明:createProductA() 和 createProductB() 分别对应两个产品等级结构,具体工厂决定返回哪个产品族的实例。客户端只持有 AbstractFactory 类型引用,切换产品族时只需换一个具体工厂。
适用场景:对象属于同一产品族需一起使用;系统不依赖具体产品类;系统只消费某一个产品族。优点是分离接口与实现,符合依赖倒转;缺点是新增产品等级结构需修改抽象工厂和所有具体工厂,违反开放-封闭。
AbstractFactory 与 Factory 的区别:AbstractFactory 为创建一组相关或依赖的对象提供接口,Factory 为一类对象提供创建接口。一个管“族”,一个管“个”。
3.2 策略模式:算法的可互换封装
策略模式的定义是:定义一系列算法,将每个算法封装到具有公共接口的独立类中,使其可互换。类的划分基础是抽象,具有相同属性和功能的对象的抽象集才是类。
interface Strategy { void doAlgorithm(); } class StrategyA implements Strategy { public void doAlgorithm() { /* 算法A */ } } class Context { private Strategy strategy; public void setStrategy(Strategy s) { strategy = s; } public void executeStrategy() { strategy.doAlgorithm(); } }Context 持有 Strategy 引用,客户端通过 setStrategy 切换算法。商场收银系统中,正常收费、满减、打折就是三个具体策略,收银台是 Context。
策略模式的关键在于:客户端必须知道有哪些策略可选,并主动选择。这和状态模式不同——状态模式的状态切换通常由上下文或状态自身驱动,客户端不感知。
3.3 外观模式与适配器模式:一个简化,一个转换
外观模式为子系统提供统一的高层接口,让子系统更容易使用。它不改变子系统本身,只是加了一层门面。适配器模式则是将一个类的接口转换成客户端期望的另一个接口,解决的是接口不兼容问题。
两者的核心区别:外观是“简化调用”,适配器是“转换接口”。外观模式下客户端直接调用外观类,不关心子系统内部;适配器模式下客户端调用目标接口,适配器内部委托被适配者。
3.4 组合模式与单例模式:树形结构与全局唯一
组合模式将对象组合成树形结构以表示“部分-整体”的层次关系,使得客户端对单个对象和组合对象的使用具有一致性。典型场景是文件系统、UI 控件树。
单例模式保证一个类只有一个实例,并提供全局访问点。复习时重点注意多线程环境下的实现方式差异:饿汉式在类加载时就创建实例,线程安全但不支持延迟加载;懒汉式需要双重检查锁定或静态内部类来兼顾延迟加载和线程安全。
3.5 中介者模式:把网状依赖变成星形依赖
中介者模式用一个中介对象封装一系列对象之间的交互,使各对象不需要显式地相互引用。迪米特法则在这里体现得最明显——同事类之间不直接通信,全部通过中介者转发。
典型场景是 GUI 对话框中多个控件之间的联动:一个下拉框的选择影响按钮是否可用、文本框是否可编辑。如果没有中介者,每个控件都要持有其他控件的引用,依赖关系变成网状;引入中介者后变成星形,每个控件只认识中介者。
4. 剩余十五种模式速览:从简单工厂到访问者
4.1 创建型补充:简单工厂、工厂方法、原型、建造者
简单工厂模式严格来说不算 GoF 二十三种之一,但它是理解工厂方法的基础。简单工厂用一个工厂类根据参数决定创建哪种产品;工厂方法将创建延迟到子类,每个具体工厂对应一种产品;抽象工厂则对应产品族。
建造者模式与工厂模式的区别:工厂模式关注“创建什么产品”,建造者模式关注“如何一步步组装一个复杂对象”。建造者通常有 buildPartA()、buildPartB()、getResult() 这样的步骤接口,适合构建过程稳定但表示可变的场景。
原型模式通过克隆现有对象来创建新对象,适用于创建成本高或需要保存对象状态的场景。与单例模式的区别:单例保证全局唯一,原型保证可以复制出多个相同实例。
4.2 结构型补充:装饰、代理、桥接、享元
装饰模式通过组合而非继承来动态扩展对象功能,符合合成/聚合原则。代理模式为其他对象提供一种代理以控制对这个对象的访问,典型场景是远程代理、虚拟代理、保护代理。
装饰器与代理的区别:装饰器关注“增强功能”,代理关注“控制访问”。装饰器通常由客户端主动组合,代理通常对客户端透明。
桥接模式将抽象部分与实现部分分离,使它们可以独立变化。享元模式通过共享技术有效支持大量细粒度对象,关键是区分内部状态(可共享)和外部状态(不可共享)。
4.3 行为型补充:模板方法、观察者、状态、命令、责任链、备忘录、访问者、迭代器、解释器
模板方法模式在父类定义算法骨架,将某些步骤延迟到子类实现。观察者模式定义一对多依赖,当一个对象状态改变时所有依赖者收到通知。状态模式允许对象在内部状态改变时改变行为,看起来像换了类。
命令模式将请求封装为对象,支持撤销和队列。责任链模式将请求沿处理者链传递直到被处理。备忘录模式在不破坏封装的前提下捕获并恢复对象内部状态。访问者模式在不改变元素类的前提下定义新操作。迭代器模式提供顺序访问聚合元素的方法。解释器模式给定语言定义文法表示及解释器。
注意:行为型模式最容易混的是策略 vs 状态、观察者 vs 中介者、责任链 vs 中介者,下一章的对比表专门处理这些。
5. 易错与易漏点:模式对比中最容易翻车的五组关系
5.1 策略 vs 状态:谁决定切换
易漏点:把策略模式和状态模式当成一回事。为什么漏:两者都有 Context 和抽象接口,UML 结构几乎一样。怎么补:记住切换的主动权不同——策略模式由客户端选择并设置策略,状态模式由上下文或状态自身驱动切换,客户端通常不感知状态变化。
5.2 适配器 vs 装饰器 vs 代理:结构相似但意图不同
易漏点:三者都是“包一层”,分不清什么时候用哪个。为什么漏:UML 类图结构高度相似,都是持有被包装对象的引用。怎么补:适配器改变接口,装饰器增强功能,代理控制访问。适配器通常包装不兼容的接口,装饰器包装同接口对象并添加行为,代理包装同接口对象并控制调用时机。
5.3 观察者 vs 中介者:一个广播,一个转发
易漏点:把观察者和中介者都当成“解耦”手段,混用。为什么漏:两者都涉及多个对象之间的通信解耦。怎么补:观察者是目标直接通知所有观察者,一对多广播;中介者是同事类之间不直接通信,全部通过中介者转发,多对多变成星形。
5.4 简单工厂 vs 工厂方法 vs 抽象工厂:维度不同
易漏点:把三个工厂当成同一维度的递进关系。为什么漏:名字都带“工厂”,容易按复杂度排序记忆。怎么补:简单工厂是“一个工厂根据参数创建多种产品”,工厂方法是“每个产品对应一个工厂”,抽象工厂是“每个工厂创建一族产品”。简单工厂是工厂方法的简化特例,抽象工厂是工厂方法的多产品等级扩展。
5.5 单例 vs 原型:唯一 vs 复制
易漏点:认为单例和原型都是“控制对象数量”的模式。为什么漏:两者都涉及对象创建的控制。怎么补:单例保证全局只有一个实例,原型保证可以复制出多个相同实例。单例限制数量,原型提供复制机制。
6. 复习清单与查漏补缺:从模式选择维度到模拟自测
复习到最后一轮,最有效的方式不是从头翻资料,而是拿一张模式选择维度表做自测。下面这张表梳理了选模式时最关键的几个判断维度。
| 判断维度 | 问题 | 对应模式 |
|---|---|---|
| 对象创建 | 创建逻辑复杂还是产品族? | 工厂方法/抽象工厂/建造者 |
| 接口不兼容 | 需要转换接口吗? | 适配器 |
| 功能扩展 | 需要动态添加功能吗? | 装饰器 |
| 访问控制 | 需要控制对象访问吗? | 代理 |
| 算法切换 | 需要运行时切换算法吗? | 策略 |
| 状态驱动 | 行为随状态改变吗? | 状态 |
| 通信解耦 | 多对象通信太复杂? | 中介者/观察者 |
| 请求处理 | 需要沿链传递请求吗? | 责任链 |
| 撤销恢复 | 需要保存和恢复状态吗? | 备忘录 |
| 树形结构 | 部分-整体层次? | 组合 |
自测方法:拿一张白纸,先默写七大原则,再按创建型、结构型、行为型三类默写所有模式名称,然后对每个模式写出“一句话定义 + 一个适用场景 + 一个易混模式”。写不出来的就是漏洞,回去翻对应章节。
// 自测代码模板:用最小代码验证是否真正理解某个模式 // 以策略模式为例,如果能不查资料写出以下结构,说明基本掌握 interface Strategy { void execute(); } class ConcreteStrategyA implements Strategy { public void execute() { System.out.println("策略A"); } } class Context { private Strategy strategy; public Context(Strategy s) { this.strategy = s; } public void run() { strategy.execute(); } } // 测试:切换策略,验证客户端无需修改参数说明:Context 构造函数注入 Strategy,run() 委托执行。如果能独立写出并解释为什么这样设计符合依赖倒转和开放-封闭,说明策略模式过关。
最后一轮复习建议按这个顺序走:先过一遍七大原则,再按三类默写模式名称,然后重点看易混淆对比表,最后用模拟题自测。我自己的习惯是每次复习设计模式都强制走一遍“原则→分类→模式→对比→自测”这条链路,因为跳过任何一步都会在面试或实际选型时卡壳。最容易忽略的其实是原则和模式之间的映射关系——很多人能背出二十三种模式的名字,但被问到“这个模式体现了哪条原则”时就答不上来,而这恰恰是区分死记硬背和真正理解的分水岭。希望这份梳理能帮到你。
本文还有配套的精品资源,点击获取