资讯动态

设计模式复习指南:从单例到策略,掌握创建型与行为型核心

发布时间:2026/10/15 6:43:14 来源:尧图企业网站定制
简介这份设计模式复习资料面向具备一定编程基础、熟悉面向对象开发的研发人员特别适合工作1至3年的程序员系统梳理与巩固核心概念。文档从设计模式的定义、分类、特点与应用场景切入逐一讲解单一职责、开放-封闭、依赖倒转、里氏代换、迪米特法则、合成/聚合及接口隔离七大原则并深入剖析抽象工厂、策略、外观、适配器、组合、单例与中介者七种常用模式同时对剩余十五种模式进行对比分析。每种模式均配有UML类图、基础代码与实例说明便于结合实际项目理解模式选型与实现细节。资源为1个docx文档整体大小约3.66MB内容详实、结构清晰涵盖模式对比、选择维度、练习题与模拟试卷可帮助读者提升代码的可维护性、扩展性和复用性。已有189人学习适合作为设计模式复习、面试准备或日常开发查阅的实用参考资料。1. 设计模式复习为什么经典模式仍然值得你花时间如果你正在准备软件工程方向的技术面试或者项目代码越写越乱、每次加需求都要动一堆老代码那设计模式复习不是纸上谈兵而是一次必要的技术复盘。很多人对设计模式的印象停留在「背 UML 图、背类名」上但真正到了实际开发里会发现模式不是模板套用而是一组在长期工程中沉淀下来的应对变化、隔离耦合、约束协作的惯用法则——它的价值主要体现在当代码需要扩展时你不需要到处打补丁而是能把新行为组装进既有结构中。这一篇会从三类共九个最常考的经典模式入手创建型、结构型、行为型围绕「为了解决什么问题、核心类如何协作、落地时什么参数最容易翻车」来拆解。每个模式都给出可运行的最小代码片段和设计取舍说明适合正在准备面试的人也适合做中期重构、觉得模块职责混乱的开发者。你自己上手跑一遍比盯着概念读十遍更有用。2. 创建型模式复习单例、工厂方法、抽象工厂的落地参数与拆解创建型模式解决的是「怎么把对象创建的过程稳定下来」的问题。它的核心逻辑是把实例化过程从业务代码中抽离使得调用方依赖的是稳定的创建入口和抽象类型而不是某个具体的类构造函数。这三者的常见误用是绕不开的我们先从最常考的单例开始讲。2.1 单例模式懒加载/饿汉式的选型与线程安全参数单例模式保证某个类全局只有一个实例全局共享访问入口。面试题目里最常见的不是「会不会写单例」而是「你的单例会怎么被破坏」。破坏手段包括反射、反序列化和多线程竞争我们在落地时应考虑这些风险再决定写法。先看一个实际用得最多的懒加载线程安全写法使用双重检查锁加 volatilepublic class ConfigManager { private static volatile ConfigManager instance; private final MapString, String configStore new ConcurrentHashMap(); private ConfigManager() { loadFromFile(); } public static ConfigManager getInstance() { if (instance null) { synchronized (ConfigManager.class) { if (instance null) { instance new ConfigManager(); } } } return instance; } }这段代码里 volatile 的作用是防止指令重排导致其他线程拿到一个尚未初始化完的对象。所谓指令重排是指 JVM 在 new 一个大对象时可能先分配内存、再执行构造函数、最后赋值给变量如果赋值在构造函数被执行完之前发生了另一个线程就会访问到一个半成品实例。这是手动实现单例最容易踩的坑不建议靠 synchronized 整个方法体来图省事锁的粒度太大了。还需要关注的是反射防护一般做法是在私有构造函数里加一个判断已有实例存在时直接抛出异常。从适用边界看单例更适合全局配置、连接池、线程池这类天然唯一的资源而不适合无状态工具类硬套单例无状态工具类用静态方法更直接。顺带一提在实际团队协作中单例往往演变成「隐藏的全局状态」成了测试的绊脚石所以现在更常见的实践是用依赖注入容器来约束单例生命周期代码里不出现 getInstance。2.2 工厂方法模式把 if-else 分支收编成子类工厂工厂方法模式意图是定义一个创建对象的接口让子类决定实例化哪一个类。它解决的是「多个相似类型之间的切换逻辑散落在业务代码里」的问题。看一个订单业务中常见的例子// 订单解析器接口 public interface OrderParser { Order parse(String rawData); } public class JsonOrderParser implements OrderParser { public Order parse(String rawData) { /* JSON 解析逻辑 */ } } public class XmlOrderParser implements OrderParser { public Order parse(String rawData) { /* XML 解析逻辑 */ } } // 工厂把格式判断收敛到一个方法里 public class OrderParserFactory { public OrderParser createParser(String format) { if (json.equalsIgnoreCase(format)) { return new JsonOrderParser(); } else if (xml.equalsIgnoreCase(format)) { return new XmlOrderParser(); } throw new IllegalArgumentException(不支持的订单格式: format); } }我把这个例子和策略模式摆在一起对比过很多次它们在结构上确实很像但意图不同。工厂方法关注的是「创建哪个对象」策略模式关注的是「替换哪个行为」。同一个 switch 分支结构如果每次分支内部都 new 出一个对象属于工厂如果分支内部是调用不同算法属于策略。初学者容易在这两者上混淆面试时讲不清意图是会被追问的。另外工厂方法适用场景里通常还伴随一句「创建逻辑相对简单」如果每个分支创建的类初始化参数差异极大建议直接写成不同工厂类而不是把分支无限堆长。2.3 抽象工厂模式产品族一致性与组合爆炸的取舍抽象工厂模式是工厂方法的升级版它解决的问题是「多个配套产品需要保持一致地创建」。最经典的例子是跨平台 UI 组件渲染一个系统里所有按钮、输入框、对话框必须风格统一。简单来说抽象工厂管了一条产品线细节是这一族产品内部有调用关联。public interface ThemeFactory { Button createButton(); Dialog createDialog(); } public class DarkThemeFactory implements ThemeFactory { public Button createButton() { return new DarkButton(); } public Dialog createDialog() { return new DarkDialog(); } } public class LightThemeFactory implements ThemeFactory { public Button createButton() { return new LightButton(); } public Dialog createDialog() { return new LightDialog(); } }抽象工厂的明显缺陷是扩展困难要新增一种产品成员比如增加一个 Menu 组件那么所有具体工厂都得实现 createMenu 方法。所以在实际应用中我会先确认产品族是否稳定如果团队的 UI 组件每周都有可能加新成员那抽象工厂大概率不是首选组合优于继承或直接面向接口编程会更灵活。在设计评审中我见过多个团队在这种问题上来回折腾为了「以后支持多主题」硬上了抽象工厂结果一年过去了主题还是两套新增组件却成了每次开发的必踩点。抽象工厂是对「稳定的产品族」做约束不是给不确定性做预留的万能方案。创建型三个模式放在一起复习时抓住一条主线单例管住一个实例工厂方法管住一个维度相似类型抽象工厂管住多个维度产品族。创建过程的复杂度和变动频率决定你选哪一个。3. 结构型模式复习适配器、装饰器、代理在真实项目里的组织方式结构型模式处理的是类与对象之间的关系目标是把不同抽象层次或不同来源的代码组合成可协作的整体。这类模式的常见特征是「类和对象的层次变多了但各自职责变得更单一」。3.1 适配器模式老接口对接新系统的三个必调位置适配器模式解决的是「接口不兼容但功能等价」的问题。老系统返回 XML新系统要 JSON第三方 SDK 暴露的方法命名与业务习惯差异很大这些都是适配器的应用场景。public class LegacyPaymentService { public boolean payByAccount(String accountId, double amount) { // 老系统支付逻辑 return true; } } public interface NewPaymentGateway { PaymentResult pay(PaymentRequest request); } // 适配器将老接口包装成新接口 public class LegacyPaymentAdapter implements NewPaymentGateway { private final LegacyPaymentService legacyService; public LegacyPaymentAdapter(LegacyPaymentService legacyService) { this.legacyService legacyService; } public PaymentResult pay(PaymentRequest request) { boolean result legacyService.payByAccount(request.getAccountId(), request.getAmount()); return PaymentResult.from(result); } }适配器模式在落地时有一个经常被忽略的边界它只能做接口转换不适合在转换过程中夹带大量业务逻辑比如加积分、发短信。如果适配器越写越厚说明老系统与现状的差距已经大到靠适配器撑不住的地步该考虑防腐层或直接重写服务了。一个比较隐蔽的坑是异常处理适配老系统可能抛出的是 ErrorCode新接口期望的是异常类型适配器里要把错误码翻译成业务异常并且要保留原始错误信息便于排查。从类图上看适配器与门面模式有些相似但意图完全不同适配器解决接口不匹配门面解决的是子系统调用复杂化。3.2 装饰器模式动态叠加能力时最容易混淆的两层边界装饰器模式的核心思想是「用组合代替继承来扩展对象功能」。它解决的问题非常具体如果一个人物类有技能树、装备、Buff用继承来实现会形成指数级的类爆炸。装饰器允许你在运行时按需把增强包在原始对象外层。// 以数据流增强为例 final class FileDataSource implements DataSource { private final String filePath; public FileDataSource(String path) { this.filePath path; } public void write(byte[] data) { /* 写文件 */ } public byte[] read() { /* 读文件 */ return new byte[0]; } } final class EncryptionDecorator implements DataSource { private final DataSource wrappee; public EncryptionDecorator(DataSource source) { this.wrappee source; } public void write(byte[] data) { byte[] encrypted encrypt(data); wrappee.write(encrypted); } public byte[] read() { byte[] raw wrappee.read(); return decrypt(raw); } }装饰器模式看起来很简单但实际使用中边界模糊点很多。最常见的混淆是装饰器和责任链装饰器要求每一层都完成同一个接口的完整行为然后在内部调用下一层责任链则允许在任意一层终止处理或者改变处理路径。另一个坑是「多层装饰后调试定位困难」。线上问题排查时看到一串六层的装饰器嵌套每一层都做一些数据的转换出了问题不好定位所以我通常在项目里约定装饰器层数不超过三层而且装饰器只做增值处理不做核心数据处理。还有一个设计决策值得反复强调当你说「要给某个类扩展功能」的时候先想清楚是不是可以通过改原类解决如果不是再看装饰器是否是最轻量级的方案。为了加一个日志输出而新建一个 LoggingDecorator通常不如直接改原类或使用 AOP 切面来得直接装饰器更适用场景是增强组合具备高度不确定性且你无法控制原类型的源码。3.3 代理模式静态代理与动态代理的控制点差异代理模式与装饰器结构非常相似关键差别在于意图——代理控制的是对原对象的「访问权限和生命周期」装饰器增强的是「功能」。「代理」和「装饰器」的类图几乎一样面试里很爱考这个区分很多人在上机题里就是因为意图讲不清丢的分。代理模式的典型应用是延迟加载。一个重量级对象初始化非常耗时但业务上不是每次调用都真正需要它那就用代理先占位。public interface ReportGenerator { String generate(); } public class HeavyReportGenerator implements ReportGenerator { public HeavyReportGenerator() { /* 模拟耗时初始化比如建立数据库连接 */ } public String generate() { return heavy report; } } public class LazyReportProxy implements ReportGenerator { private HeavyReportGenerator realGenerator; public String generate() { if (realGenerator null) { realGenerator new HeavyReportGenerator(); } return realGenerator.generate(); } }动态代理在这里更工程化。Java 的 Proxy 和字节码增强技术可以拦截所有方法调用统一做权限校验、日志记录业务代码里基本无感知。比如某个服务的每个方法都要校验角色权限手写代理类太重复动态代理可以在不改变原类的前提下统一处理。注意动态代理是有性能损耗的调用频繁的小方法被代理后会明显变慢所以线上某些极端高并发路径会把动态代理换成编译期静态代理或字节码直接生成。在实际项目中代理模式经常与 Spring AOP 混着提但这不是同一个概念。AOP 是切面思想代理是它的实现机制之一你复习的时候不要把它们强行画等号要说清「AOP 可以通过动态代理实现但不是只能用它实现」。4. 行为型模式复习策略、观察者、模板方法如何驱动业务逻辑演进三类模式里行为型最贴近业务代码因为它管理的不是「创建」也不是「结构」而是「对象之间的交互方式和职责分配」。「策略」「观察者」「模板方法」也是面试准备中必背的三件套。4.1 策略模式与 if-else 重构的边界什么情况下值得做策略模式的用途一句话就能说明白定义一族算法使它们可以互相替换让算法的变化独立于使用它的客户端。打折活动、运费计算、消息发送渠道这类业务是最经典的使用场景。public interface DiscountStrategy { double compute(double price, User user); } public class VipDiscount implements DiscountStrategy { public double compute(double price, User user) { if (gold.equals(user.getLevel())) return price * 0.8; return price * 0.9; } } public class NoDiscount implements DiscountStrategy { public double compute(double price, User user) { return price; } } // 运行时选择 DiscountStrategy strategy DiscountStrategyFactory.get(user.getLevel()); double finalPrice strategy.compute(price, user);策略模式的核心收益在于「消除分支不是目的隔离变化才是」。很多开发者看到一个 switch 就急着改成策略模式这是比较常见的过度设计。分支本身不可怕只有满足下面两个条件时才值得替换一是分支内逻辑在持续膨胀每次加新分支都要动同一个方法修改频率很高二是算法确实会在运行时动态切换。如果两者都不满足一个简单的 switch 可读性更好。还要注意策略模式会引入大量小类管理成本是实际存在的。我在某真实项目里见过十几个策略类每个类就两三行代码更合适的做法是把策略类改成枚举加函数式接口大幅减少类文件数量。Java 8 之后的新写法是策略模式的一种演进如果你在面试里提到「用枚举Function 替代策略类」是一个不错的加分点。4.2 观察者模式事件通知中的同步阻塞与异常传播盲区观察者模式定义对象间一对多的依赖关系当一个对象状态改变时所有依赖它的对象都得到通知。电商下单后的一系列后续动作——扣库存、发短信、送积分、更新推荐——是它最典型的应用。public class OrderEventBus { private final ListOrderListener listeners new CopyOnWriteArrayList(); public void register(OrderListener listener) { listeners.add(listener); } public void publish(OrderEvent event) { for (OrderListener listener : listeners) { listener.onOrderCreated(event); } } }这个简单实现里的第一个坑是同步阻塞所有监听器按注册顺序依次执行前面一个监听器慢后面的全部被拖住。某开发者在项目里就吃过亏积分服务响应变慢导致整个下单接口超时。解决方向有几种最直接的是把监听器丢进线程池执行但要注意线程池不会继承原有的事务上下文和 traceId排查问题会变难。第二个坑是异常传播。上面的循环里任何一个 listener 抛出异常后面的监听器都不会执行了而且异常会一路传到下单入口让用户看到一个下单失败但其实订单已经创建成功的奇怪状态。现实项目中一般会约定监听器内部自己捕获异常或者由事件总线提供隔离执行的能力保证一个监听器失败不影响其他监听器。4.3 模板方法模式固化的流程骨架与变动的步骤节点模板方法模式定义一个操作中的算法骨架把一些步骤延迟到子类中实现。它解决的问题是「流程是稳定的但流程里某些步骤的实现会因为场景不同而变化」——比如数据导入的流程永远是读文件、校验、转换、落库但每一个具体步骤在不同数据源下有不同实现。public abstract class DataImporter { // 流程骨架固定不变 public ImportResult execute(File file) { Data data readFile(file); validate(data); ListEntity entities convert(data); int count save(entities); return ImportResult.success(count); } protected abstract Data readFile(File file); protected abstract void validate(Data data); protected abstract ListEntity convert(Data data); protected abstract int save(ListEntity entities); }模板方法模式最容易被忽视的点是钩子方法缺失。如果骨架里有些步骤在特定场景下不需要执行比如「读 Excel」场景不需要校验正确的做法是在基类里提供一个默认实现或一个可覆盖的钩子而不是在子类里强行把方法体写空。写空是一种反模式它相当于用子类行为去破坏父类定义的流程契约。另外一个争议点是模板方法模式和策略模式都强调可替换但模板方法偏向「同一流程的多版本复用」策略模式偏向「流程某个环节算法替换」如果整个流程骨架都会变说明模板方法不适合当前场景应该考虑策略模式或从更上层重构。5. 设计模式的复习路线与排查怎么从会用走向选对模式不是学完就结束了。大多数工程师的卡点不在「不会写某个模式」而在「面对一个具体问题时选错了模式或选对了却写跑偏」。这一章给出一个实操性较强的复习路线也把高频崩溃点摊开讲。5.1 适合软件工程面试与日常重构的两周复习路线设计模式的知识密度不算低但不值得拉长战线。通常按两周来排即可一周过概念和代码一周做集中应用训练再留两三天做交叉复盘。第一周聚焦「每个模式的三要素」解决的问题、核心参与类、适用边界。以单例为例你要能说清它解决全局唯一资源访问问题核心类只有一个 Singleton 自己边界是依赖注入容器或枚举方式实现时语义会变。第二周要做的是「模式联想训练」拿到一个场景描述先别急着写代码先说出你想到的模式并说明理由。比如「消息推送支持短信、邮件、站内信」——这是策略模式「每个推送渠道都要记录日志、重试和限流」——这是装饰器模式。复习顺序上建议先结构型后行为型结构型枚举明确好上手行为型关联业务语义更重放到后面理解门槛更低。一个普遍的误区是「用背 UML 代替场景理解」——UML 里一个类的职责和历史背景一旦脱离场景很快就会忘记。5.2 模式落地排查选对应付不过去的一刻把所有模式能出现的问题归一下类排在最前面的其实是「为了用模式而用模式」。一个单方法不超过 30 行的小模块硬拆出接口、工厂、策略三件套后续的维护成本已经超过了它带来的收益。所以在排查任何模式应用时我的第一问不是「这个类在不在它该在的位置」而是「这一层抽象是否真的隔离了变化」。如果答案是「没有任何新需求来了还是得改这里」那这个模式就是无效的。第二个常见问题是「把创建型模式做成全局单例滥用」。工厂类本身也是类如果把工厂也做成单例且内部持有大量线程不安全的成员变量多线程环境下照样翻车。第三个问题是「区分不清组合和继承的边界」。装饰器和策略模式都在提倡组合优于继承但具体到场景里当你在两个相似的类中间复制粘贴方法实现时就要停下来想想是抽父类还是提取策略对象。这三个问题每天在真实代码评审里反复出现属于比较高频的踩坑点。遇到这些问题时先反思模式边界这比直接重写代码更能提高整体判断力。6. 设计模式复习的最后一公里把模式的能量放到复盘与评审中带着真实项目走一遍设计模式复盘比刷多少道概念题都更有用。我建议你现在就把最近一个月写过的代码文件列表拉出来按模式维度给自己打个分。比如你的订单服务里有没有不自觉写出的 if-else 分支长廊你的接口实现类有没有悄悄超过五个方法、明明可以拆策略却还在往里加这种复盘会直接告诉你下一步的实践方向。另外一个值得养成的习惯是在代码评审时问一句如果用现有的一个经典模式重构这段代码会更好还是更糟这一问能把设计模式从「知识储备」变成「日常工具」。某位前辈的做法就很聪明——要求组内新人在每个迭代里至少提交一份「模式重构申请」写清楚现状代码的问题、要使用的模式、预期的收益和可能引入的复杂度。大部分申请会被驳回但被驳回过程本身就是一次设计复盘。技术能力就是在这种「反复衡量和取舍」中练出来的而不是在做完需求后的夸夸其谈里。最后我分享一个自己踩过的坑。某个功能用了策略模式管理多种通知渠道代码结构本身没有问题但我在策略类里直接 new 了依赖对象没有走依赖注入结果后续做单元测试时无法 mock 依赖重构难度极大。从那以后我给自己定了一条规矩用到模式的同时必须把对象创建和依赖关系放在容器或工厂层模式负责行为编排容器负责生命周期管理两者分工明确就不会再互相拖累。设计模式复习不是「背下来就完事」它要求你在创建、结构、行为三个维度上都能回答「为什么是它」和「什么时候不是它」。希望这篇能帮你把知识打散重组在面试和项目里都真正用得上。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑