news 2026/10/11 9:04:21

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 Map<String, DiscountStrategy> strategyMap; public OrderAmountService(Map<String, 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类被写成了final,CGLIB就代理不了。

实际开发中,动态代理是把"横切逻辑"从业务代码里抽出来的利器。后面讲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适配成字符流Reader
Iterator迭代器对集合的遍历行为标准化,不暴露内部结构
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管代理,事件管观察者,模板方法管流程,每个模式都待在最适合自己的位置上。

我在实际项目里体会最深的一点是:设计模式的功夫始终在代码之外。你把变化点想清楚了,代码长成什么样,策略还是模板方法还是工厂,往往是自然涌现的结果;反过来,脑子里先摆好一堆模式名然后再找地方硬套,写出来的东西就会别扭。所以与其纠结"这个模式怎么用",不如先问自己一句:这个模块里,到底什么会变?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 9:04:12

登记测试与验收测试报告:区别、风险与实操安排

1. 两个报告到底差在哪&#xff1a;先从名字背后的“出身”说起登记测试报告和验收测试报告&#xff0c;名字里都有“测试”两个字&#xff0c;但这两个东西从头到尾就不是一回事。我见过太多项目方&#xff0c;拿着登记测试报告去应付项目验收&#xff0c;结果被甲方打回来重新…

作者头像 李华
网站建设 2026/10/11 9:03:22

agent-skills 实战:从技能定义到编排,构建可落地的 AI 智能体执行体系

1. 从“会聊”到“会做”&#xff1a;agent-skills 到底在解决什么问题这两年跟不少做 AI 应用的朋友聊&#xff0c;大家有个共同的感受&#xff1a;模型本身越来越聪明&#xff0c;但真让它去干一件具体的事&#xff0c;往往还是“嘴上功夫”。你问它“帮我整理一下这周的会议…

作者头像 李华
网站建设 2026/10/11 8:57:25

PostgreSQL + pgvector + RRF 混合检索替代向量数据库的落地实践

这事得从一笔账单说起。去年年底&#xff0c;项目里的向量数据库服务快到期了&#xff0c;我看了眼续费单&#xff0c;再对照我们过去三个月的实际调用量&#xff0c;心底那杆秤就开始晃了。随后我花了一个周末&#xff0c;把基于 PostgreSQL 的方案搭了出来&#xff1a;pgvect…

作者头像 李华
网站建设 2026/10/11 8:56:03

workbuddy自定义指令配置指南:从零搭建高效AI协作体系

1. 从“它听不懂”到“它比我还靠谱”&#xff1a;自定义指令到底解决了什么问题刚上手 workbuddy 这类智能协作工具的时候&#xff0c;我踩过最大的一个坑就是&#xff1a;默认配置下它确实能干活&#xff0c;但干出来的活总差那么点意思。你让它整理会议纪要&#xff0c;它给…

作者头像 李华
网站建设 2026/10/11 8:54:54

灵活用工新生态平台:企业级灵活用工平台技术方案与系统开发选型指南

摘要随着共享经济、新业态用工快速发展&#xff0c;灵活用工平台已经从简单的佣金代发工具&#xff0c;演变为集任务撮合、电子签约、智能风控、财税结算、数据审计于一体的企业级新生态系统。很多企业在选型时&#xff0c;容易只看报价、功能页面&#xff0c;忽略合规资质、底…

作者头像 李华