ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

设计模式复习实战指南:从七大原则到重点模式代码落地

设计模式复习实战指南:从七大原则到重点模式代码落地

简介:这份《大话设计模式复习》文档面向具备一定面向对象编程基础、工作1至3年的研发人员,系统梳理设计模式的核心知识体系。内容从设计模式的定义、分类、特点与应用场景切入,详细讲解单一职责、开放-封闭、依赖倒转、里氏代换、迪米特法则、合成/聚合及接口隔离七大设计原则,并深入剖析抽象工厂、策略、外观、适配器、组合、单例、中介者七种常用模式,以及其余十五种模式,每种均配以定义、UML类图、基础代码与实例。文档还横向对比不同模式的差异,总结模式选择的关键维度,并附练习题与模拟试卷辅助巩固。资源包为单个docx文件,约3.66MB,结构完整、目录清晰,便于按章节检索学习。目前已有191人学习,适合希望提升代码可维护性、可扩展性与复用能力,并在实际项目中灵活选用设计模式的开发者参考。

1. 从一次代码评审翻车说起:为什么设计模式复习不能只背 23 个名字

去年帮一个做企业级管理系统的团队看代码,需求是加一种新的支付渠道。按理说这是个小改动,结果开发同学在PaymentService里又塞了一个else if,那个类已经膨胀到 1800 多行,改完之后回归测试直接炸了三个老渠道。问题不在他不会写代码,而在于他脑子里没有「策略模式」这个可替换的抽象层——所有渠道逻辑硬编码在一个类里,扩展一次就动一次核心逻辑,开放-封闭原则形同虚设。

这就是设计模式复习这件事最尴尬的地方:很多人能背出 23 个模式的名字和分类,但真到项目里该用的时候想不起来,或者用错了地方。这份《大话设计模式复习》文档的价值,恰恰在于它不是干巴巴地列定义,而是把七大设计原则、七大重点模式(抽象工厂、策略、外观、适配器、组合、单例、中介者)和剩余 15 种模式全部配了 UML 类图、基础代码和真实实例,最后还做了易混淆模式的横向对比和模拟试卷。它适合两类人:一是工作 1-3 年、想从「能写功能」进阶到「能设计结构」的开发者;二是正在准备软件工程方向课程设计或毕设答辩、需要一套完整模式应用综述来支撑方案的同学。下面我按「需求怎么立 → 结构怎么设计 → 代码怎么落地 → 怎么验收」这条线,把这份复习资料拆成一份可以直接照着走的方案。

2. 需求与技术选型:先想清楚复习设计模式到底要解决什么问题

2.1 复习目标不是记住 23 个名字,而是建立「问题→模式」的映射

大多数人复习设计模式的失败路径是这样的:打开文档从「单例模式」开始看,看到「访问者模式」已经忘了前面一半,合上文档脑子里只剩「好像有个工厂」。根本原因是把设计模式当成了知识点来背,而不是当成工具箱来用。

正确的复习目标应该反过来定:先明确你手头有哪些典型问题场景,再倒推该用哪个模式。比如「对象创建逻辑复杂且需要解耦」对应工厂系列,「算法需要运行时切换」对应策略模式,「子系统接口太复杂需要简化」对应外观模式,「不兼容接口需要转换」对应适配器模式。这份文档里七大重点模式之所以被单独拎出来分析,就是因为它们在真实项目中的出现频率最高,抽象工厂管对象族创建、策略管算法互换、外观管接口简化、适配器管接口转换、组合管树形结构、单例管全局唯一、中介者管多对象解耦——每一个都对应一类反复出现的工程问题。

所以复习的第一件事,是给自己列一张「我最近写过的代码里,哪些地方改起来最痛苦」的清单,然后带着这些问题去对照模式。这比从头背到尾效率高得多。

2.2 技术栈与文档结构:Java 为主、C++ 补充,UML 贯穿始终

这份复习资料的技术选型很明确:主体代码用 Java 实现,抽象工厂和策略模式的实例部分有 C++ 版本作为对照。这个选择是合理的,因为 Java 的接口和抽象类机制天然适合演示面向对象的设计模式,而 C++ 的多态和虚函数则能帮你理解模式在更底层语言里的形态差异。

文档的整体结构分四层:

层次内容作用
基础层设计模式定义、分类、特点、应用场景、UML 类图规范建立统一术语和表达方式
原则层七大设计原则(SRP、OCP、DIP、LSP、LOD、CARP、ISP)提供评判设计好坏的标准
重点模式层抽象工厂、策略、外观、适配器、组合、单例、中介者高频模式深度剖析,含完整代码
扩展与对比层剩余 15 种模式 + 易混淆模式对比 + 练习题与模拟试卷补齐知识面,强化辨析能力

这个结构的聪明之处在于:先用原则建立判断标准,再用重点模式打透几个典型场景,最后用对比表把容易混淆的模式区分开。复习的时候如果时间紧,可以按「原则 → 七大模式 → 对比表 → 模拟试卷」的顺序走,把剩余 15 种模式作为查阅手册而不是精读材料。

2.3 适用人群与前置知识:会写类不等于会做设计

这份资料明确说了适合「具备一定编程基础、对面向对象设计有了解」的研发人员。翻译一下就是:你得能看懂 Java 的 interface、abstract class、继承和多态,知道什么是 UML 类图中的泛化、实现、关联、聚合、组合关系。如果这些还不熟,直接看模式代码会很吃力。

具体来说,前置知识包括三块:一是面向对象三大特性(封装、继承、多态)的实际运用经验,不是概念背诵;二是 UML 类图的基本读图能力,至少能分清实线空心三角是继承、虚线空心三角是实现;三是至少写过 3000 行以上的项目代码,否则你很难体会到「这个类改起来真痛苦」是什么感觉,也就无法理解模式要解决的真实痛点。

提示:如果你目前只写过课程作业级别的小程序,建议先找一个开源项目读一读它的分层结构,再回来复习模式,效果会好很多。

3. 总体设计:七大原则打底、三类模式分层、对比表收口的复习框架

3.1 七大设计原则是判断模式用得好不好的标尺

文档把七大原则放在模式分析之前,这个顺序不能颠倒。因为设计模式是「术」,设计原则是「道」——你不知道什么是好的设计,就没法判断一个模式用得对不对。

七大原则里最核心的是三条:开放-封闭原则(OCP)要求软件实体对扩展开放、对修改关闭,这是所有模式追求的终极目标;依赖倒转原则(DIP)要求高层模块和低层模块都依赖抽象,这是实现 OCP 的主要手段;单一职责原则(SRP)要求一个类只有一个引起变化的原因,这是控制类膨胀的第一道防线。

剩下四条各有侧重:里氏代换原则(LSP)保证子类能替换父类而不破坏行为契约,迪米特法则(LOD)要求对象之间尽量少直接通信、通过中间人转发,合成/聚合原则(CARP)优先用组合而非继承来降低耦合,接口隔离原则(ISP)要求把大接口拆成小接口避免依赖不需要的方法。

复习的时候我建议这样用:每看完一个模式,回头对照它「应用了哪些原则、违反了哪些原则」。比如文档里抽象工厂模式的分析就明确写了——它应用了依赖倒转原则和合成/聚合原则,但在新增产品等级结构时会违反开放-封闭原则。这种「有得有失」的分析才是真正理解模式的关键,而不是简单地说「这个模式好」。

3.2 三类模式的分层逻辑:创建型管怎么生、结构型管怎么拼、行为型管怎么动

23 种设计模式按目的分为三大类,这个分类不是随便分的,它对应了软件设计中三个最基本的问题维度:

创建型模式(5 种)解决的是「对象怎么创建」的问题。单例模式保证全局唯一实例,工厂方法模式把创建延迟到子类,抽象工厂模式创建整个产品族,建造者模式分步构建复杂对象,原型模式通过克隆创建新对象。它们的共同目标是:把对象的创建和使用分离,让客户端不依赖具体类。

结构型模式(7 种)解决的是「类和对象怎么组合」的问题。适配器模式转换接口,装饰器模式动态添加职责,代理模式控制访问,外观模式简化子系统接口,桥接模式分离抽象与实现,组合模式处理树形结构,享元模式共享细粒度对象。它们的共同目标是:通过组合关系构建更灵活的结构。

行为型模式(10 种)解决的是「对象之间怎么交互和分配职责」的问题。策略模式封装可互换算法,观察者模式建立一对多依赖通知,命令模式把请求封装为对象,状态模式让对象在内部状态改变时改变行为,责任链模式沿链传递请求,模板方法模式定义算法骨架,迭代器模式顺序访问集合元素,中介者模式集中管理对象间交互,备忘录模式捕获和恢复状态,访问者模式在不改变元素类的前提下定义新操作。

复习时按这个分层走,每类先抓 2-3 个最常用的模式吃透,再扩展到同类其他模式,比按字母顺序或难度顺序背要高效得多。

3.3 对比表是复习收口的关键工具

文档最后一部分的对比表是我认为最有价值的内容之一。它把容易混淆的模式成对或成组地放在一起比较:

  • 简单工厂 vs 工厂方法 vs 抽象工厂:从「一个工厂类管所有产品」到「一个工厂接口管一类产品」再到「一个工厂接口管一族产品」,抽象层级逐步提升
  • 适配器 vs 装饰器 vs 代理:三者都涉及「包装」一个对象,但适配器改变接口、装饰器增强功能、代理控制访问
  • 策略 vs 状态:两者都通过组合持有可变行为,但策略是客户端主动选择算法,状态是对象内部状态变化驱动行为切换
  • 观察者 vs 中介者:观察者是一对多的广播通知,中介者是多对多的集中协调
  • 责任链 vs 中介者:责任链是请求沿链传递直到被处理,中介者是所有交互都经过中心节点

这些对比不是让你背结论,而是让你在选型时能快速排除不合适的选项。比如你面对一个「需要动态切换算法」的场景,看到「策略 vs 状态」的对比就知道:如果切换是客户端主动发起的,选策略;如果是对象内部状态变化触发的,选状态。

4. 核心模块实现:从抽象工厂到中介者的代码落地与设计说明

4.1 抽象工厂模式:可移植数据访问层的完整实现

抽象工厂是文档里篇幅最长的模式分析,因为它涉及的产品族概念最复杂。文档给的实例是「可移植数据访问程序」——同一套业务逻辑需要支持 SQL Server 和 Access 两种数据库,每种数据库都要创建 User 和 Department 两类对象。

先看抽象工厂接口和具体工厂的定义:

// 抽象工厂接口:声明创建产品族的方法 public interface IFactory { IUser CreateUser(); // 创建 User 对象的接口 IDepartment CreateDepartment(); // 创建 Department 对象的接口 } // SQL Server 具体工厂:创建 SQL Server 产品族 public class SqlServerFactory implements IFactory { public IUser CreateUser() { return new SqlserverUser(); // 返回 SQL Server 的 User 实现 } public IDepartment CreateDepartment() { return new SqlserverDepartment(); } } // Access 具体工厂:创建 Access 产品族 public class AccessFactory implements IFactory { public IUser CreateUser() { return new AccessUser(); } public IDepartment CreateDepartment() { return new AccessDepartment(); } }

这段代码的关键设计点在于:客户端只依赖IFactory、IUser、IDepartment三个抽象,完全不感知具体用的是哪个数据库。切换数据库时只需要换一个工厂实例,业务代码一行不用改。

// 客户端代码:只依赖抽象接口 public class Client { public static void main(String[] args) { // 切换数据库只需改这一行 IFactory factory = new SqlServerFactory(); // IFactory factory = new AccessFactory(); IUser iu = factory.CreateUser(); iu.Insert(new User()); iu.GetUser(1); IDepartment id = factory.CreateDepartment(); id.Insert(new Department()); id.GetDepartment(1); } }

参数说明:IFactory是抽象工厂接口,SqlServerFactory和AccessFactory是具体工厂,IUser和IDepartment是抽象产品接口,SqlserverUser、AccessUser、SqlserverDepartment、AccessDepartment是具体产品。客户端通过工厂接口获取产品,通过产品接口调用业务方法。

这个模式的优缺点文档写得很清楚:优点是分离接口和实现、客户端与具体产品解耦、增加产品族时符合依赖倒转原则;缺点是增加新的产品等级结构(比如再加一个IScore接口)时,需要修改抽象工厂和所有具体工厂,违反开放-封闭原则。所以抽象工厂适合「产品族稳定、产品等级结构可能扩展」的场景,反过来就要慎重。

4.2 策略模式:商场收银系统的算法互换设计

策略模式解决的是「算法需要运行时切换」的问题。文档用的实例是商场收银系统,不同折扣策略对应不同的收费算法。

// 策略接口:声明算法方法 interface Strategy { void doAlgorithm(); } // 具体策略 A 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()方法在运行时注入不同的策略对象,然后统一调用executeStrategy()。这样新增一种折扣算法只需要实现一个新的Strategy子类,不需要修改Context和已有策略类,完全符合开放-封闭原则。

文档还给了 C++ 版本的收银系统实现,核心思路一致,只是用虚函数替代了 Java 的接口。对比看两种语言的实现能帮你理解策略模式的本质:不管语言怎么变,「定义算法族 → 封装每个算法 → 使它们可互换」这个结构是不变的。

策略模式应用的设计原则包括:开放-封闭原则(新增策略不改已有代码)、依赖倒转原则(Context 依赖 Strategy 抽象而非具体策略)、合成/聚合原则(Context 通过组合持有 Strategy 而非继承)。它和状态模式的区别在于:策略模式的切换由客户端主动发起,状态模式的切换由对象内部状态变化驱动。

4.3 单例模式与中介者模式:全局唯一与多对多解耦

单例模式是文档里篇幅较长的另一个模式,因为它的实现方式有多种(饿汉式、懒汉式、双重检查锁、静态内部类、枚举),每种都有线程安全和性能上的取舍。核心目标只有一个:保证一个类只有一个实例,并提供一个全局访问点。

// 双重检查锁实现(兼顾线程安全和延迟加载) public class Singleton { // volatile 禁止指令重排序,防止拿到未初始化完的对象 private static volatile Singleton instance; private Singleton() {} // 私有构造,禁止外部 new public static Singleton getInstance() { if (instance == null) { // 第一次检查:避免不必要的同步 synchronized (Singleton.class) { if (instance == null) { // 第二次检查:确保只创建一次 instance = new Singleton(); } } } return instance; } }

参数说明:volatile关键字是必须的,它禁止 JVM 指令重排序,防止在instance = new Singleton()这行代码中,对象还没初始化完成就把引用赋值给instance,导致其他线程拿到一个半成品对象。这是单例模式最容易翻车的地方,也是面试高频考点。

中介者模式解决的是「多个对象之间交互复杂、耦合严重」的问题。文档里的定义是:用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用。同事类之间不直接通信,全部通过中介者转发。

// 抽象中介者 abstract class Mediator { public abstract void send(String message, Colleague colleague); } // 抽象同事类 abstract class Colleague { protected Mediator mediator; public Colleague(Mediator mediator) { this.mediator = mediator; } } // 具体同事类 A class ConcreteColleagueA extends Colleague { public ConcreteColleagueA(Mediator mediator) { super(mediator); } public void send(String message) { mediator.send(message, this); // 通过中介者转发 } public void notify(String message) { // 接收来自中介者的通知 } }

设计说明:中介者模式把多对多的网状依赖变成了一对多的星形依赖,同事类之间不再直接引用,所有交互都经过中介者。这符合迪米特法则——两个类若不必直接通信,就不发生直接作用。代价是中介者本身可能变得很复杂,如果同事类太多、交互逻辑太杂,中介者会膨胀成一个「上帝类」。所以中介者模式适合交互规则明确且相对稳定的场景,不适合交互逻辑频繁变化的场景。

5. 避坑与常见问题:复习设计模式最容易翻车的五个点

5.1 把模式当模板硬套,为了用而用

现象:项目里明明只有一个数据库、一种支付方式,却非要上抽象工厂和策略模式,代码量翻了三倍,维护的人一脸懵。

原因:把「用了多少模式」当成了设计能力的体现,忽略了模式是用来解决特定问题的工具,不是目的。

解决:每次想引入一个模式之前,先问自己三个问题——不用这个模式会怎样?未来真的会有多种实现吗?引入后代码是更简单还是更复杂了?如果答案是「不用也能跑」「短期内不会扩展」「引入后更复杂」,那就先别用。模式是等到痛点出现时才上的,不是提前铺路的。

5.2 混淆策略模式和状态模式

现象:写了一个策略模式,但策略的切换逻辑写在 Context 内部,根据某个字段值自动切换,结果和状态模式长得一模一样,自己都说不清用的是哪个。

原因:两个模式的类图结构几乎相同(都是 Context 持有抽象接口,接口有多个实现),区别只在「谁驱动切换」和「切换的语义」。

解决:记住一条判断标准——如果切换是客户端主动选择的(比如用户选了「打八折」),用策略模式;如果切换是对象内部状态变化自动触发的(比如订单从「待支付」变成「已支付」后行为自动改变),用状态模式。策略模式的 Context 通常提供setStrategy()方法供外部调用,状态模式的 Context 通常内部管理状态转换。

5.3 单例模式忘记处理线程安全

现象:本地测试一切正常,上线后偶尔出现两个单例实例,导致配置不一致或资源重复初始化。

原因:用了懒汉式但没有加锁,或者加了锁但没加volatile,多线程环境下指令重排序导致拿到未初始化完的对象。

解决:如果对延迟加载没有硬性要求,直接用饿汉式(类加载时就创建实例,JVM 保证线程安全);如果需要延迟加载,用双重检查锁加volatile,或者用静态内部类方式(利用 JVM 的类加载机制保证线程安全且实现延迟加载)。最省事的是枚举单例,天然线程安全且防止反射攻击。

5.4 抽象工厂新增产品等级结构时大面积改代码

现象:原本只支持 User 和 Department 两类对象的数据库访问层,现在要加一个 Score 对象,结果抽象工厂接口、两个具体工厂类、所有相关代码都要改。

原因:抽象工厂模式的设计目标是「增加产品族容易,增加产品等级结构困难」。产品族是横向的(SQL Server 族、Access 族),产品等级结构是纵向的(User、Department、Score)。纵向扩展必然要改工厂接口。

解决:如果产品等级结构经常变化,抽象工厂不是好选择,可以考虑用简单工厂配合反射,或者改用工厂方法模式。如果产品等级结构稳定、产品族可能扩展,抽象工厂才是合适的。选型前先判断哪个维度更可能变化。

5.5 外观模式把不该简化的接口也简化了

现象:为了「让调用方便」,写了一个外观类把子系统所有接口都包了一层,结果子系统新增功能时外观类也要跟着改,而且有些调用方需要精细控制子系统,外观类反而成了障碍。

原因:外观模式的定位是「为子系统提供一个简化的统一接口」,它不应该屏蔽子系统的所有能力,只应该封装那些高频、复杂的组合调用。

解决:外观类只封装最常用的几组操作,不要试图覆盖子系统的全部功能。需要精细控制的地方,允许调用方直接访问子系统。外观模式的价值在于降低客户端与子系统的耦合,不是完全隔离。

6. 验收与答辩:用模拟试卷和对比表做一轮自检

复习到最后,怎么判断自己真的掌握了而不是「看过了」?这份文档给了一个很实用的收口方式:练习题加模拟试卷。我的建议是把它当成验收环节来用,而不是当额外的负担。

具体操作分三步走。第一步,合上文档,拿一张白纸,把七大设计原则和 23 种模式按创建型、结构型、行为型三类默写出来,每个模式用一句话写清楚「它解决什么问题」。写不出来的就是没记住的,回去补。第二步,打开文档最后的对比表,遮住结论部分,自己先判断每组模式的区别,再对照文档看说得对不对。比如「适配器 vs 装饰器 vs 代理」这组,你能不能准确说出适配器改变接口、装饰器增强功能、代理控制访问?说不清楚就回去看对应章节。第三步,做模拟试卷,重点看错题涉及的是哪个模式、哪个原则,针对性补漏。

答辩场景下,有几个高频问题值得提前准备。一是「你这个项目里用了哪些设计模式,为什么选它不选别的」——回答时一定要结合具体场景,说清楚不用这个模式会有什么问题。二是「抽象工厂和工厂方法有什么区别」——抓住「一个产品族 vs 一个产品等级结构」这个核心差异。三是「策略模式和状态模式怎么区分」——用「客户端主动切换 vs 内部状态驱动切换」来回答。四是「单例模式有哪些实现方式,各自优缺点是什么」——至少能说出饿汉式、双重检查锁、静态内部类三种。

我自己的习惯是:每次复习完一个模式,就找一个自己写过的项目代码,看看能不能用这个模式重构一小块。能重构成功,说明真懂了;重构完发现更复杂了,说明这个场景不适合用这个模式,也是一种收获。从那以后我每次准备设计模式相关的答辩或评审,都强制走一遍「默写 → 对比 → 重构」这三步,比反复看文档有效得多。

希望这份拆解能帮你把设计模式复习从「背了忘、忘了背」的循环里拉出来,真正变成一套能用在项目和答辩里的方案。完整文档里还有剩余 15 种模式的详细分析和更多实例代码,需要的话可以拿去对照着过一遍。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进