2026/10/11 9:04:22

Java设计模式实战指南:从源码到框架,把背八股变成用得上

Java设计模式实战指南:从源码到框架,把背八股变成用得上 聊到Java设计模式很多人的第一反应是23种模式的名字和定义接着就是那句经典的感叹背倒是背过项目里真用不上。我这些年面过不少人也被面过不少次最深的感受是设计模式面试题从来不是考你背了多少而是考你在真实代码里有没有识别变化点的能力。所以这次我写一篇实战向的Java设计模式指南从原理、代码到框架应用把背八股变成用得上。这篇文章适合谁一类是准备Java面试的人能把设计模式答出项目实践感而不是背教科书另一类是工作几年的后端开发发现自己一直在写重复的if-else和散落各地的对象创建逻辑想看看框架里那些精巧结构到底是怎么组织的。我会把高频模式讲透再带你把Spring、MyBatis和JDK源码里真实出现的模式一个个认出来最后聊聊那些最典型的用错模式现场。1. 设计模式不是背出来的先把23种模式的族谱理顺1.1 为什么背熟23种模式还是写不出好代码先说一个我经常在项目里看到的现象有的人张口就能背出开闭原则、里氏替换原则代码里却满屏的new和互相纠缠的if-else而有些没有系统学过设计模式的老开发写出来的代码却隐隐约约符合某个模式的样子。原因很简单。设计模式不是一套规定它是前人踩过坑之后沉淀下来的场景解法。每个模式背后都有一个明确的问题场景和一组约束条件你脱离场景去背它的结构和名称自然不知道什么时候该用。就好比你背熟了下雨天要带伞这句话但落到实际你还得判断那一天到底下不下雨、你出门时间有多久、包里放不放得下伞。设计模式也一样先看到场景才知道该掏出哪把伞。那怎么把场景和模式对应起来我的经验是不要从模式名字出发要从变化点出发。所谓变化点就是你代码里未来最可能发生变化的地方——支付方式会变、报表格式会变、事件发生后要通知的人会变。设计模式的核心思想本质上就是一句话封装变化优先组合面向接口编程。当你理解了这句话再看23种模式你会发现它们全是在不同的层次上回答如果这里变了怎么让改动最小。1.2 一张表看穿分类逻辑创建型、结构型、行为型23种模式常被分成三类很多初学者记不住是因为不知道这个分类角度到底是什么。其实逻辑特别直白你就问三个问题分类关注的问题模式列表一句话记忆创建型对象怎么来的单例、工厂方法、抽象工厂、建造者、原型把new打散让创建逻辑不散落在客户端结构型类和对象怎么组合适配器、装饰器、代理、外观、桥接、组合、享元在继承和组合之间做权衡扩展结构而不破坏现有代码行为型对象之间怎么协作模板方法、策略、观察者、状态、命令、责任链、迭代器、中介者、备忘录、访问者、解释器把会变的行为/算法/协作关系封装成可替换的单元注意同一个代码结构在不同视角下可以是不同模式分类本身不是目的理解这个模式在解决哪个层面的问题是关键。比如代理模式被归为结构型因为它介于调用方和被调用方之间改变的是调用结构而模板方法和策略都涉及行为替换所以落到行为型。面试时你如果能说出这一层含义而不是干巴巴报分类名立刻就不一样了。1.3 哪些模式真正值得优先投入23个模式不是平均用力的。我翻了这些年读过和写过的项目代码结合Spring、MyBatis、Netty这些常见框架真正高频率、值得优先吃透的其实就六个单例、工厂、策略、模板方法、代理、观察者。这六个模式覆盖了日常编码中绝大多数对象创建分支过多流程固定但环节可变横切逻辑事件通知的场景。第二梯队是建造者、适配器、装饰器、责任链、状态、迭代器、外观。它们出场频率不低但通常出现在某个特定场景——比如参数很多用建造者接口不兼容用适配器流式处理用装饰器多个校验器依次执行用责任链。第三梯队的桥接、享元、命令、中介者、备忘录、访问者、解释器反而不是说没用而是它们在Java日常业务开发里出现的概率相对低更多集中在框架底层、编译器、编辑器这类基础软件中。我建议初学者把时间花在第一梯队上先把这六个模式练到看到场景就能想到它的程度再按需要补第二梯队。我自己带团队时也是这么要求的——能把这六个模式讲明白、写出来、在项目里指出对应位置就已经超过绝大多数只会背定义的人了。2. 高频模式的原理与代码单例、工厂、策略、模板方法、代理、观察者2.1 单例模式五种写法与一个volatile单例模式是Java面试里几乎必考的模式但也是最容易被写错的一个。它的意图一句话讲完保证一个类在整个JVM生命周期内只有一个实例并提供一个全局访问点。适合无状态的工具类、配置类、连接池等资源类。注意是资源类如果你把可变的业务状态塞进单例里多线程环境下基本就是在埋雷。从写法上看有五种经典实现饿汉式类加载时就创建实例。线程安全因为类加载过程由JVM保证只会执行一次。缺点是如果这个类一直没用到实例也会提前创建造成无谓的内存占用。懒汉式线程不安全版首次调用时才创建。单线程没问题多线程下两个线程可能同时走到if (instance null)各自new出一个对象。双重检查锁DCL这是最常被问的写法。public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }这里有两个细节经常被面试官追问。为什么要volatile因为instance new Singleton()在JVM里不是原子操作它分三步分配内存、调用构造方法初始化、把引用指向内存。如果没有volatile编译器和CPU可能对后面两步重排序导致另一个线程在第一个if处读到instance ! null但拿到的对象其实还没完成初始化。volatile禁止了这种重排序。为什么要双重判断第一次判断是为了避免不必要的锁竞争第二次判断是在拿到锁之后再次确认防止两个线程同时穿过第一次判断、排队进入同步块后重复创建对象。静态内部类利用JVM的类加载机制既实现延迟加载又天然线程安全是很多项目里推荐的写法。枚举不仅线程安全还能防反射攻击和序列化破坏Joshua Bloch在Effective Java里明确推荐但在实际项目中用得反而不多主要是因为枚举的语义让人一眼看不出这是个单例容器。我踩过最典型的坑是把一个单例类设计成了可变配置中心某个线程改了一个字段其他线程读到的状态全乱了。所以后来我给自己定了一条规矩单例只承载无状态服务或只读配置任何需要在运行时被修改的东西一律不进单例。2.2 工厂模式简单工厂、工厂方法、抽象工厂到底差在哪工厂模式的核心是解决对象创建逻辑散落的问题。假设你现在有支付宝、微信、银行卡三种支付渠道最粗暴的写法是在支付接口里写一个大的if-else每加一种渠道就改一次支付类违背开闭原则。简单工厂的做法是把创建逻辑收敛到一个类里public class PayStrategyFactory { public static PayStrategy create(String channel) { if (alipay.equals(channel)) { return new AlipayStrategy(); } if (wechat.equals(channel)) { return new WechatPayStrategy(); } throw new UnsupportedOperationException(不支持的支付渠道: channel); } }简单工厂本身不是23种设计模式之一它是一个常用的编码手法。缺点很明显新增渠道还是要改这个工厂类。工厂方法模式则更进一步把创建逻辑下沉到子类public interface PayFactory { PayStrategy create(); } public class AlipayFactory implements PayFactory { Override public PayStrategy create() { return new AlipayStrategy(); } }客户端不再依赖具体支付类只依赖PayFactory和PayStrategy接口。注意工厂方法的意义不是消灭if-else而是把选择创建哪一个的决策推迟。至于抽象工厂它是为了解决产品族的问题同一套工厂能产生一系列配套的对象。比如你不仅有支付渠道还有每个渠道对应的对账单解析器、退款处理器这时你就需要一个抽象工厂把支付宝全家桶和微信全家桶分别生产出来。在实际项目里Spring的Bean方法从某种意义上就是工厂方法的实现——Spring容器负责创建和管理Bean业务类不关心Bean怎么来的。这个模式在框架应用里还会再展开。2.3 策略模式把if-else变成可插拔的算法族策略模式在我看来是性价比最高的一个模式因为它能直接改善日常代码里最让人头疼的分支爆炸问题。它的意图是定义一族算法把每个算法封装起来让它们可以互相替换且替换不影响客户端。看一个具体的重构例子。你有一个订单金额计算服务会员等级不同折扣算法不同public interface DiscountStrategy { BigDecimal calculate(BigDecimal amount); } public class VipDiscountStrategy implements DiscountStrategy { Override public BigDecimal calculate(BigDecimal amount) { return amount.multiply(new BigDecimal(0.8)); } } public class NormalDiscountStrategy implements DiscountStrategy { Override public BigDecimal calculate(BigDecimal amount) { return amount; } }客户端的使用方式变成public class OrderAmountService { private final MapString, DiscountStrategy strategyMap; public OrderAmountService(MapString, DiscountStrategy strategyMap) { this.strategyMap strategyMap; } public BigDecimal calculate(String vipLevel, BigDecimal amount) { DiscountStrategy strategy strategyMap.get(vipLevel); if (strategy null) { throw new IllegalArgumentException(未知会员等级: vipLevel); } return strategy.calculate(amount); } }这里有个实战经验策略模式用的不是少了if-else而是把if-else的决策点收敛到了一处。strategyMap就是那张决策表由Spring注入时自动组装。新增一个会员等级你只需要新增一个实现类并注册到容器里OrderAmountService完全不用改。但要注意区分策略模式和状态模式。策略解决的是同一件事的不同做法比如折扣算法、压缩算法状态解决的是同一个对象在不同状态下对同一动作给出的不同反应比如订单在待支付、已支付、已取消状态下调用取消方法行为完全不一样。面试官经常把这两个放一起问本质就是在考察你真的理解区别还是只是背了个名字。2.4 模板方法模式固定骨架与可变步骤的组合模板方法模式适合这样一种场景一段业务逻辑的流程骨架是固定的但其中某些步骤的具体实现会变化。比如数据迁移无论从哪个数据源迁移到哪个目标库流程都是固定的校验参数、抽取数据、转换数据、加载数据、校验结果。如果把整段迁移逻辑写在每一个实现类里公共流程会大量重复如果不加约束不同人还可能写出完全不同的迁移顺序。模板方法用继承来解决public abstract class DataMigrationTemplate { // 骨架方法建议 final防止子类篡改流程 public final void migrate() { validate(); extract(); transform(); load(); verify(); } protected void validate() { // 默认校验逻辑子类可重写 } protected abstract void extract(); protected abstract void transform(); protected abstract void load(); protected void verify() { // 默认校验逻辑子类可重写 } }这样每个子类只关心抽取、转换、加载这三个真正会变的步骤公共流程被牢牢锁在父类里。你可能会问这和策略模式有什么区别最简单的区分方式模板方法用继承控制流程策略用组合替换整个算法。模板方法适合流程固定、环节变化策略适合整个做法都可以替换。其实你早就用过模板方法了。想想AbstractQueuedSynchronizer——AQS就是典型的模板方法框架acquire、release这些方法定义了获取和释放锁的骨架把tryAcquire、tryRelease留给子类实现。还有Spring的JdbcTemplate它把获取连接、创建语句、处理结果、释放资源这条固定链路封装起来了把变化的SQL和参数映射通过回调暴露出去。严格来说JdbcTemplate更准确的说法是模板方法回调它不是靠继承而是靠传入回调对象来替换变化部分这个细节面试时点一下很加分。2.5 代理模式静态代理和JDK动态代理代理模式解决的是不想直接调用目标对象希望在调用前后加一些逻辑的问题。典型场景有日志、权限校验、事务管理、远程调用。把一个业务类和一个日志类硬编码在一起每加一种日志逻辑就要改业务代码用代理把横切逻辑抽出来业务类就不用关心了。静态代理很容易理解给UserService写一个UserServiceProxy实现同样的接口内部持有UserService在调用前后插入日志。缺点也很明显——每代理一个类就要写一个代理类代理逻辑还不能复用。JDK动态代理就灵活得多public class LogProxy { public static Object create(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) - { System.out.println(before: method.getName()); Object result method.invoke(target, args); System.out.println(after: method.getName()); return result; } ); } }这套机制的核心是InvocationHandler所有代理逻辑都集中在一个invoke方法里被代理的类不用改。注意JDK动态代理有个硬性要求目标对象必须实现接口。如果目标对象没有接口可以用CGLIB生成子类代理但目标类不能是final方法也不能是final。Spring AOP在目标Bean实现接口时默认用JDK动态代理没有接口时自动切到CGLIB这也是不少人在配置AOP时踩坑的地方——ServiceImpl类被写成了finalCGLIB就代理不了。实际开发中动态代理是把横切逻辑从业务代码里抽出来的利器。后面讲Spring AOP时会看到事务注解之所以能生效很大程度上就是动态代理在背后起作用。要特别注意的是动态代理不是魔法调用对象内部方法时代理逻辑不一定能触发比如this调用这在Spring事务里是经典的自调用失效问题。2.6 观察者模式事件驱动的基础设施观察者模式的意图是定义对象间一对多的依赖关系当一个对象状态变化时所有依赖它的对象都会收到通知。典型场景是下单成功后要做一堆事发短信、发邮件、扣减库存、优惠券发放、积分累计。如果全写在OrderService里每加一个事件后动作就要改一次OrderService而且动作之间还可能互相干扰。用观察者模式OrderService只负责下单并发布一个订单创建成功事件各监听者自己决定要不要响应、怎么响应。下面是一个简化的Spring事件写法SpringBootApplication public class OrderEventDemo { // 事件对象 public record OrderCreatedEvent(Long orderId, Long userId) {} // 发布方 Service public static class OrderService { private final ApplicationEventPublisher publisher; public OrderService(ApplicationEventPublisher publisher) { this.publisher publisher; } public void createOrder(Long userId) { // 1. 创建订单 Long orderId 10001L; // 2. 发布事件 publisher.publishEvent(new OrderCreatedEvent(orderId, userId)); } } // 监听方1发短信 Component public static class SmsListener { EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println(发送短信给用户: event.userId()); } } // 监听方2发优惠券 Component public static class CouponListener { EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println(给订单发放优惠券: event.orderId()); } } }OrderService发布事件后根本不知道谁会响应也不关心响应的顺序。新增一个发送站内信的监听者只需要加一个新的EventListener方法没有任何人需要改这就是开闭原则的体现。设计事件对象时我建议做成不可变对象只携带必要上下文避免事件在多个监听者之间流转时被某个监听者修改导致后续监听者拿到脏数据。这个模式是事件驱动架构的基石。你要真把观察者模式吃透了后面理解消息队列里的发布订阅模型也会顺很多因为它们虽然技术形态不同但思想完全一致。3. 框架应用Spring、MyBatis 和 JDK 里的模式身影3.1 Spring一份可以直接回答面试题的模式地图Spring是设计模式最集中的样板间。我当时学设计模式时最兴奋的事情就是在Spring源码里一个个认出它们的真实用法。以下这张表基本覆盖了Spring中最常见的设计模式应用设计模式Spring中的应用位置场景说明单例模式Bean的默认Scope一个IoC容器中同一个Bean默认只有一个实例相当于框架级的单例管理工厂模式BeanFactory、FactoryBean把对象创建的细节收归容器业务代码只声明依赖不直接new代理模式AOP、Transactional切面增强和事务管理的底层机制有接口走JDK动态代理无接口走CGLIB模板方法JdbcTemplate、RestTemplate、DispatcherServlet固定访问流程把变化步骤通过回调或子类暴露出去观察者模式ApplicationEvent、EventListener事件发布与监听实现数据变更后的解耦通知责任链模式HandlerInterceptor、OncePerRequestFilter多个拦截器/过滤器按顺序处理请求可任意增删策略模式HandlerMapping、HandlerAdapter、Resource实现类同一抽象接口按场景选择不同实现如不同资源访问方式面试时最值钱的三个点是工厂、代理、观察者。工厂对应BeanFactory——你想想如果没有容器统一管理Bean你每用一个Service就得自己把它的依赖链全部new出来那将是灾难代理对应AOP——事务、日志、权限这些横切逻辑可以不污染业务代码全靠代理在运行时做增强观察者对应事件机制——Spring事务提交成功后发事件通知下游做数据同步。另外有个细节我特别喜欢DispatcherServlet处理请求的整体流程是固定的——接收请求、查找Handler、调用Handler、解析视图、渲染响应这就是一个模板方法结构的骨架而HandlerMapping、HandlerAdapter又是策略接口允许扩展各种URL映射方式和参数解析方式。这就是模板方法定骨架策略定细节的组合用法在真实框架里非常常见。3.2 JDK你每天都在用却没意识到的模式除了框架JDK源码里也到处都是设计模式的身影。面试被问到JDK里有哪些设计模式时大部分人的第一反应都是Runtime是单例但能多说几个的很少。我帮你整理了一条完整的记忆链JDK中的位置设计模式说明Runtime.getRuntime()单例一个JVM进程只有一个Runtime对象Integer.valueOf()享元-128到127的Integer对象从缓存取避免重复创建String常量池享元字面量字符串复用同一个对象BufferedInputStream包装FileInputStream装饰器在不改变InputStream接口的前提下增强缓冲能力InputStreamReader适配器把字节流InputStream适配成字符流ReaderIterator迭代器对集合的遍历行为标准化不暴露内部结构Comparator策略同一个集合可以按不同比较策略排序Collections.unmodifiableList装饰器/不可变包装给集合加一层只读外壳防止外部修改面试时讲这些例子不要只报名字。至少挑一两个说清楚为什么它是。比如Integer.valueOf为什么是享元因为它把高频使用的整数对象缓存起来所有使用Integer.valueOf(100)的代码拿到的是同一个对象引用节省了对象创建开销。聊到这里可以顺嘴提一句如果面试官问不用new Integer而用valueOf怎么回答这就是那个问题的隐藏考点。还有一个容易被忽略的点Comparator是策略模式Collections.sort接收不同的Comparator排序算法不变但排列规则可以任意切换。你在写业务时设计可变策略的参照物就是它。3.3 MyBatis一个小而精的模式集合体MyBatis体量比Spring小但设计模式密度很高。第一个是工厂模式SqlSessionFactory负责创建SqlSession业务代码不关心SqlSession底层怎么获取连接、怎么和数据库打交道。这和我们前面讲的工厂模式完全对应。第二个是代理模式MyBatis最大的特点就是Mapper接口只有定义、没有实现类却能正常执行SQL。原因就是MyBatis在启动时会用JDK动态代理为每个Mapper接口生成一个代理对象这个代理对象把接口方法调用转换为对SqlSession的SQL执行。所以你天天用的userMapper.selectById(1)本质上走的是一套代理链路。第三个是模板方法BaseExecutor定义了对JDBC操作的整体骨架——获取连接、处理参数、执行SQL、处理结果集、关闭资源把doQuery、doUpdate等细节留给子类实现。第四个是责任链MyBatis的插件机制就是通过拦截器链实现的多个拦截器按顺序对目标方法进行层层环绕每个插件只处理自己关心的逻辑。这些模式组合起来才让MyBatis保持轻量但扩展能力强的特性。如果你在面试中说我读过MyBatis源码发现它的Mapper是动态代理实现的这句话的含金量远高于背出代理模式定义。4. 面试官问设计模式时其实想听你说什么4.1 面试官真正考察的是权衡能力不是背定义我当面试官时最怕听到的答案是一字不差地背概念模板方法模式是定义一个操作中的算法的骨架而将一些步骤延迟到子类中。你说得对但这没有信息量。我想听到的是你项目里哪一个具体场景让你想到了这个模式用了之后解决了什么牺牲了什么。设计模式本质上是一组权衡下的选择。引入工厂模式增加了类数量和抽象层级但换来了创建逻辑的集中和扩展性引入策略模式类数量也会增加但消除了巨型if-else。面试官想确认的不是你知不知道这个模式存在而是你有没有能力在真实业务里判断这里值不值得用模式用哪个最合适。所以回答设计模式问题我建议用一个固定套路先说场景再说解法最后说代价。4.2 高频面试题的答题框架四类问法一次捋清我把设计模式面试题大致分成四类每类的答题侧重点不一样。手写型典型的题目是手写一个线程安全的单例。这个问题考察基本功建议直接写DCL版本然后主动补一句这里加了volatile是为了禁止指令重排序不然另一个线程可能拿到未初始化完成的对象。主动解释比被追问解释要加分得多。源码型典型的题目是Spring里用了哪些设计模式。答题框架是分类讲不要一股脑堆名字。先说工厂BeanFactory统一管理Bean创建再说代理AOP底层动态代理事务因此生效然后模板方法JdbcTemplate固定流程、观察者ApplicationEvent解耦通知。每讲一个就补一个具体类和它的使用场景这样面试官不会觉得你在背列表。重构型典型的题目是if-else太多怎么用设计模式优化。答题的第一步不是上来就写策略模式而是先问自己这些分支是同一类算法的不同实现吗如果是用策略模式如果这些分支是不同条件下走不同流程可能是责任链或状态模式如果每个分支只是返回不同常量那根本用不着设计模式。能说出先判断再选型这个思路就是你与背模板答案的人的区别。对比型常见的对比包括模板方法和策略有什么区别装饰器和代理有什么区别工厂方法和抽象工厂有什么区别。这类题的关键是先找共同点再找本质区别。比如模板方法和策略都解决了算法变化的问题但模板方法通过继承重用骨架策略通过组合替换整个算法。装饰器和代理都包装了目标对象但装饰器是增强功能代理是控制访问。4.3 记忆技巧怎么把23种模式装进一张触发表网上流传着各种23种设计模式记忆口诀比如单工建原抽、适桥组装外享代之类的。我不是说口诀不好但纯背公式容易变成知道名字、不知道干嘛。我更推荐一种触发式记忆法记住一句场景话对应一个模式。你会在什么时候说出这句话大概率需要的模式这个对象全局只能有一个单例我希望创建逻辑不散落在客户端工厂这件事有多种做法以后还可能加策略流程骨架不变但某些环节要换模板方法我不想直接调用原对象想在前后加点逻辑代理这个事件发生后要通知一群人观察者不能改源码但想增强现有类的能力装饰器接口不匹配需要转换一下适配器多个处理器按顺序执行每个都可能拦截责任链参数太多构造方法都快爆炸了建造者这张表是我自己总结出来的面试前快速过一遍比背口诀管用得多。因为你面试时面临的永远是场景而不是模式名称。5. 项目实战防坑模式用对了是重构用错了是过度设计5.1 三个最典型的滥用场景第一个是为模式而模式。我在评审代码时见过一个项目系统里只有一个实现类也强行加了一个接口加一个工厂每加一个字段要改三处。这种抽象毫无意义增加的是阅读成本和维护成本。判断标准很简单如果没有多个实现可以切换、没有明显的变化点就不要引入工厂或者接口。模式的目的是应对变化不是证明代码很优雅。第二个是策略滥用。有的团队一看到if-else就上策略模式结果策略类膨胀到几十个每个类里只有三四行代码而且类之间的差异很小。这种情况下我建议先用枚举加函数式接口收敛比如枚举 Function既保留了策略模式的扩展性又避免类爆炸。第三个是单例滥用。单例很轻便容易被顺手用到各种类上但一旦单例里保存了可变状态高并发环境下就是巨大的隐患。比如把当前用户ID放在单例里两个线程同时处理不同用户的请求互相覆盖查出来的数据全是串的。我在实践中的底线是单例里只放无状态服务、只读配置、线程安全组件别的都不放。5.2 判断该不该用模式先找变化点与其背一堆XX模式适用场景不如掌握一套统一的判断方法。我的做法是画一条线找出这个模块未来最可能变化的维度把变化维度对齐到对应的模式。如果变化的是对象怎么创建对齐工厂模式。如果变化的是同一件事的算法实现且种类会增多对齐策略模式。如果变化的是流程中某个步骤的实现而骨架稳定对齐模板方法。如果变化的是一个事件发生后需要通知哪些对象对齐观察者。如果变化的是调用目标对象时想附加横切逻辑对齐代理模式。如果变化的是多个处理器按顺序依次执行的编排对齐责任链。这套对齐逻辑比背场景表更本质。因为同一个业务需求不同人看会看到不同的变化维度也就可能设计出不同的模式组合。没有唯一的正确答案只有当前约束下相对更合理的解法。还要提醒一点如果不是当前阶段真实存在的需求哪怕预判未来可能变我也不会急着上模式。过度设计比不使用模式更可怕因为抽象有成本团队新人理解这套抽象也有成本。我习惯的做法是第一版先把代码写直白等确认了变化点再来重构。设计模式的价值在重构时体现得最明显。5.3 一个订单场景里自然组合多种模式最后用一个常见的订单支付场景把前面的模式串起来。假设你要开发一个支付模块需求是支持支付宝、微信、银行卡三种渠道而且支付渠道一定会增加。支付流程是固定的校验参数、预下单、发起支付、处理回调、对账。支付完成后还要发短信、发邮件、记日志。这样设计就非常自然支付渠道是变化点用策略模式定义PayStrategy接口每个渠道一个实现渠道实例的创建可能带着渠道特有的配置用工厂模式把创建过程包装起来整体流程固定用模板方法定义一个AbstractPayFlow把doPrePay、doPay、handleCallback留给渠道子类实现支付完成事件用观察者模式OrderService发布事件短信、邮件、日志各挂一个监听者日志和权限校验等横切逻辑再用动态代理或者责任链挂上去。你发现没有这些模式之间不是互斥的它们各自封住了不同维度的变化点。这个场景里没有一个模式是为了显得厉害而硬塞的每一个都对应了一个真实可能发生的变化。这种多种模式协同的组合用法才是设计模式在真实项目里的常态。后来我再看各种框架源码时发现Spring也是这样做的容器管工厂AOP管代理事件管观察者模板方法管流程每个模式都待在最适合自己的位置上。我在实际项目里体会最深的一点是设计模式的功夫始终在代码之外。你把变化点想清楚了代码长成什么样策略还是模板方法还是工厂往往是自然涌现的结果反过来脑子里先摆好一堆模式名然后再找地方硬套写出来的东西就会别扭。所以与其纠结这个模式怎么用不如先问自己一句这个模块里到底什么会变