Java 设计模式实用笔记
设计模式不是为了把代码写得很“高级”,而是为了解决反复出现的代码组织问题。
比如后端开发里经常遇到这些场景:
- 支付方式越来越多:微信、支付宝、银行卡。
- 通知渠道越来越多:短信、邮件、站内信。
- 订单状态越来越复杂:待支付、已支付、已取消、已退款。
- 接口调用前后要加日志、鉴权、限流、缓存。
- 多个业务步骤有固定流程,但每一步细节又不同。
如果所有逻辑都堆在 if else 里,代码很快就会变得难读、难测、难扩展。
设计模式的核心作用就是:让变化的地方更容易变化,让稳定的地方保持稳定。
一、设计模式先解决什么问题
学习设计模式时,不要先背名字,而要先想它解决什么问题。
| 问题 | 常见模式 |
|---|---|
| 对象怎么创建更灵活 | 工厂、建造者、单例 |
| 多种算法或规则怎么切换 | 策略 |
| 固定流程里某些步骤不同 | 模板方法 |
| 请求要经过多个处理节点 | 责任链 |
| 不改原类的情况下增强能力 | 代理、装饰器 |
| 老接口和新接口不兼容 | 适配器 |
| 一个事件发生后通知多个对象 | 观察者 |
设计模式不是越多越好。项目刚开始很简单时,直接写清楚就行。等代码里出现明显重复、分支爆炸、扩展困难时,再考虑引入模式。
二、单例模式:全局只需要一个对象
单例模式保证一个类在系统中只有一个实例。
常见场景:
- 配置管理器。
- 线程池。
- 本地缓存对象。
- 工具类实例。
简单写法:
public class AppConfig {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() { }
public static AppConfig getInstance() { return INSTANCE; }}使用:
AppConfig config = AppConfig.getInstance();不过在 Spring 项目里,很多对象默认就是单例 Bean:
@Servicepublic class OrderService {}默认情况下,Spring 容器里只会有一个 OrderService 实例。所以真实项目里,不一定要手写单例,交给 Spring 管理通常更合适。
注意:单例不要滥用。全局可变状态太多,会让代码难测试、难排查问题。
三、工厂模式:把创建对象的逻辑收起来
工厂模式用来封装对象创建过程。
假设有多个支付渠道:
public interface PayService { void pay(String orderNo);}微信支付:
public class WeChatPayService implements PayService { @Override public void pay(String orderNo) { System.out.println("微信支付:" + orderNo); }}支付宝支付:
public class AliPayService implements PayService { @Override public void pay(String orderNo) { System.out.println("支付宝支付:" + orderNo); }}如果业务代码里到处这样写:
if ("wechat".equals(payType)) { return new WeChatPayService();} else if ("alipay".equals(payType)) { return new AliPayService();}后续支付方式越多,代码越乱。
可以抽一个工厂:
public class PayServiceFactory {
public static PayService getPayService(String payType) { if ("wechat".equals(payType)) { return new WeChatPayService(); } if ("alipay".equals(payType)) { return new AliPayService(); } throw new IllegalArgumentException("不支持的支付方式:" + payType); }}调用方只关心拿到支付服务:
PayService payService = PayServiceFactory.getPayService("wechat");payService.pay("ORDER_1001");工厂模式的好处是:创建逻辑集中管理,调用方不用关心具体实现类怎么 new 出来。
四、策略模式:消灭一堆 if else
策略模式适合处理“多种规则、多种算法、多种业务分支”的场景。
比如订单优惠:
- 普通用户不打折。
- VIP 用户打 9 折。
- 新用户立减 20 元。
先定义策略接口:
public interface DiscountStrategy { int calculate(int originAmount);}普通用户:
public class NormalDiscountStrategy implements DiscountStrategy { @Override public int calculate(int originAmount) { return originAmount; }}VIP 用户:
public class VipDiscountStrategy implements DiscountStrategy { @Override public int calculate(int originAmount) { return (int) (originAmount * 0.9); }}新用户:
public class NewUserDiscountStrategy implements DiscountStrategy { @Override public int calculate(int originAmount) { return Math.max(originAmount - 20, 0); }}上下文类:
public class DiscountContext {
private final DiscountStrategy strategy;
public DiscountContext(DiscountStrategy strategy) { this.strategy = strategy; }
public int calculate(int originAmount) { return strategy.calculate(originAmount); }}使用:
DiscountStrategy strategy = new VipDiscountStrategy();DiscountContext context = new DiscountContext(strategy);
int finalAmount = context.calculate(100);策略模式适合把一堆分支拆成多个小类。后续新增一种优惠规则,只要新增一个策略类,不需要改一大段 if else。
在 Spring 中更常见的写法是把策略 Bean 放进 Map:
@Servicepublic class DiscountService {
private final Map<String, DiscountStrategy> strategyMap;
public DiscountService(Map<String, DiscountStrategy> strategyMap) { this.strategyMap = strategyMap; }
public int calculate(String userType, int amount) { DiscountStrategy strategy = strategyMap.get(userType); if (strategy == null) { throw new IllegalArgumentException("不支持的用户类型:" + userType); } return strategy.calculate(amount); }}这种写法在支付、导出、通知、审核规则里都很常见。
五、模板方法模式:固定流程,不同步骤
模板方法模式适合“流程固定,但部分步骤不同”的场景。
比如不同文件导入流程都类似:
读取文件 -> 校验数据 -> 保存数据 -> 返回结果但 Excel、CSV、JSON 的读取方式不同。
抽象模板:
public abstract class ImportTemplate {
public final void importData(String filePath) { String content = readFile(filePath); validate(content); save(content); afterImport(); }
protected abstract String readFile(String filePath);
protected void validate(String content) { System.out.println("校验数据"); }
protected void save(String content) { System.out.println("保存数据"); }
protected void afterImport() { System.out.println("导入完成"); }}Excel 导入:
public class ExcelImportTemplate extends ImportTemplate { @Override protected String readFile(String filePath) { return "读取 Excel 文件:" + filePath; }}CSV 导入:
public class CsvImportTemplate extends ImportTemplate { @Override protected String readFile(String filePath) { return "读取 CSV 文件:" + filePath; }}模板方法的关键是:父类控制流程,子类只改变化的步骤。
Spring 里很多类都有模板方法思想,比如 JdbcTemplate。它把连接获取、异常处理、资源释放这些固定流程封装起来,开发者只关心 SQL 和参数。
六、责任链模式:让请求一关一关处理
责任链模式适合一个请求要经过多个处理节点的场景。
常见例子:
- 登录请求:参数校验 -> 验证码校验 -> 密码校验 -> 权限校验。
- 下单请求:库存校验 -> 优惠券校验 -> 风控校验 -> 创建订单。
- 网关请求:黑名单校验 -> 限流 -> 鉴权 -> 转发。
定义处理器接口:
public interface OrderCheckHandler { void check(OrderContext context);}库存校验:
public class StockCheckHandler implements OrderCheckHandler { @Override public void check(OrderContext context) { System.out.println("校验库存"); }}优惠券校验:
public class CouponCheckHandler implements OrderCheckHandler { @Override public void check(OrderContext context) { System.out.println("校验优惠券"); }}责任链执行器:
public class OrderCheckChain {
private final List<OrderCheckHandler> handlers;
public OrderCheckChain(List<OrderCheckHandler> handlers) { this.handlers = handlers; }
public void check(OrderContext context) { for (OrderCheckHandler handler : handlers) { handler.check(context); } }}上下文对象:
public class OrderContext { private Long userId; private Long productId; private Integer count;}责任链的好处是:每个处理器只负责一件事,新增校验规则时,新增一个 Handler 即可。
Spring MVC 的拦截器、Filter 链,本质上也有责任链思想。
七、代理模式:不改原类,增强功能
代理模式是在不修改原对象的情况下,给它增加额外能力。
常见场景:
- 记录日志。
- 统计耗时。
- 权限校验。
- 事务管理。
- 缓存增强。
接口:
public interface UserService { void createUser(String username);}真实对象:
public class UserServiceImpl implements UserService { @Override public void createUser(String username) { System.out.println("创建用户:" + username); }}代理对象:
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) { this.target = target; }
@Override public void createUser(String username) { long start = System.currentTimeMillis(); target.createUser(username); long cost = System.currentTimeMillis() - start; System.out.println("耗时:" + cost + "ms"); }}使用:
UserService userService = new UserServiceProxy(new UserServiceImpl());userService.createUser("zhangsan");Spring AOP 就大量使用了代理模式。比如 @Transactional 并不是把事务代码写进你的方法里,而是通过代理在方法执行前后开启、提交或回滚事务。
八、装饰器模式:一层一层增强对象
装饰器模式和代理模式有点像,都是不直接修改原类来增强功能。
区别可以简单理解为:
- 代理更强调控制访问,比如权限、事务、远程调用。
- 装饰器更强调功能叠加,比如加糖、加冰、加珍珠。
举个通知发送例子:
public interface Notifier { void send(String message);}基础短信通知:
public class SmsNotifier implements Notifier { @Override public void send(String message) { System.out.println("短信通知:" + message); }}增加日志装饰:
public class LogNotifierDecorator implements Notifier {
private final Notifier notifier;
public LogNotifierDecorator(Notifier notifier) { this.notifier = notifier; }
@Override public void send(String message) { System.out.println("发送前记录日志"); notifier.send(message); System.out.println("发送后记录日志"); }}使用:
Notifier notifier = new LogNotifierDecorator(new SmsNotifier());notifier.send("订单支付成功");Java IO 流就是典型装饰器模式:
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("a.txt"));FileInputStream 负责读取文件,BufferedInputStream 在外面加了一层缓冲能力。
九、适配器模式:让不兼容的接口一起工作
适配器模式适合新老接口不一致、第三方接口和系统接口不一致的场景。
比如系统内部统一支付接口:
public interface PayClient { void pay(String orderNo, int amount);}但某个第三方 SDK 的方法是这样的:
public class ThirdPartyPaySdk { public void createPayment(String tradeNo, int money) { System.out.println("第三方支付:" + tradeNo + ",金额:" + money); }}可以写一个适配器:
public class ThirdPartyPayAdapter implements PayClient {
private final ThirdPartyPaySdk sdk;
public ThirdPartyPayAdapter(ThirdPartyPaySdk sdk) { this.sdk = sdk; }
@Override public void pay(String orderNo, int amount) { sdk.createPayment(orderNo, amount); }}业务代码仍然只依赖 PayClient:
PayClient payClient = new ThirdPartyPayAdapter(new ThirdPartyPaySdk());payClient.pay("ORDER_1001", 100);这样以后换支付 SDK,只需要改适配器,不需要大面积改业务代码。
十、观察者模式:一个事件通知多个对象
观察者模式适合“一个事件发生后,要触发多个后续动作”的场景。
比如订单支付成功后:
- 修改订单状态。
- 增加用户积分。
- 发送短信通知。
- 推送站内信。
- 记录支付日志。
如果全部写在支付方法里,方法会越来越长。
定义事件:
public class OrderPaidEvent { private String orderNo;
public OrderPaidEvent(String orderNo) { this.orderNo = orderNo; }
public String getOrderNo() { return orderNo; }}监听器接口:
public interface OrderPaidListener { void onPaid(OrderPaidEvent event);}短信监听器:
public class SmsOrderPaidListener implements OrderPaidListener { @Override public void onPaid(OrderPaidEvent event) { System.out.println("发送支付成功短信:" + event.getOrderNo()); }}积分监听器:
public class PointOrderPaidListener implements OrderPaidListener { @Override public void onPaid(OrderPaidEvent event) { System.out.println("增加用户积分:" + event.getOrderNo()); }}事件发布器:
public class OrderEventPublisher {
private final List<OrderPaidListener> listeners;
public OrderEventPublisher(List<OrderPaidListener> listeners) { this.listeners = listeners; }
public void publish(OrderPaidEvent event) { for (OrderPaidListener listener : listeners) { listener.onPaid(event); } }}Spring 里也有事件机制:
applicationEventPublisher.publishEvent(new OrderPaidEvent(orderNo));观察者模式可以让主流程更干净,但也要注意:事件太多时,调用链会变得不直观,需要做好日志和监控。
十一、建造者模式:创建复杂对象更清晰
如果一个对象字段很多,构造方法会变得很长:
Order order = new Order(1L, "ORDER_1001", 100, 1, "北京", "13800000000");这种写法很难看出每个参数代表什么。
可以使用建造者模式:
public class Order {
private Long userId; private String orderNo; private int amount; private int status; private String address; private String phone;
private Order() { }
public static Builder builder() { return new Builder(); }
public static class Builder { private final Order order = new Order();
public Builder userId(Long userId) { order.userId = userId; return this; }
public Builder orderNo(String orderNo) { order.orderNo = orderNo; return this; }
public Builder amount(int amount) { order.amount = amount; return this; }
public Builder status(int status) { order.status = status; return this; }
public Builder address(String address) { order.address = address; return this; }
public Builder phone(String phone) { order.phone = phone; return this; }
public Order build() { return order; } }}使用:
Order order = Order.builder() .userId(1L) .orderNo("ORDER_1001") .amount(100) .status(1) .address("北京") .phone("13800000000") .build();建造者模式适合字段多、可选参数多、对象创建过程比较复杂的场景。
十二、什么时候不要用设计模式
设计模式不是越多越好。
下面这些情况可以先别急着套模式:
- 代码只有两三个分支,而且短期不会扩展。
- 项目还在原型阶段,需求变化非常快。
- 团队成员都不熟悉某个模式,强行使用会增加理解成本。
- 为了“看起来高级”而拆出大量接口和类。
不推荐:
一个简单的新增用户逻辑,拆成 UserFactory、UserBuilder、UserStrategy、UserProcessor、UserContext。更推荐:
先写清楚,等重复和变化真的出现,再抽象。设计模式的目的不是炫技,而是让代码更容易维护。
十三、我常用的判断方式
写业务代码时,可以这样判断要不要用模式:
| 现象 | 可以考虑 |
|---|---|
同一个方法里很多 if else,每个分支是一种规则 | 策略模式 |
| 多个类流程一样,只有个别步骤不同 | 模板方法 |
| 一个请求要经过多个校验或处理节点 | 责任链 |
| 想在方法前后加日志、事务、权限 | 代理模式 |
| 想给对象一层层叠加功能 | 装饰器模式 |
| 第三方接口和系统接口不一致 | 适配器模式 |
| 一个事件发生后要触发多个动作 | 观察者模式 |
| 创建对象很复杂,构造参数很多 | 建造者模式 |
学习设计模式最好的方式不是背定义,而是结合真实业务反复问自己:
这里变化的是什么?这里稳定的是什么?如果下个月新增一种规则,我要改多少代码?如果一个设计能让新增需求时少改老代码、少影响主流程、少制造重复逻辑,那它就是有价值的设计。
总结
设计模式可以简单分成三类:
- 创建型:解决对象怎么创建,比如单例、工厂、建造者。
- 结构型:解决类和对象怎么组合,比如代理、装饰器、适配器。
- 行为型:解决对象之间怎么协作,比如策略、模板方法、责任链、观察者。
对后端开发来说,最常用的不是所有 23 种模式,而是少数几个高频模式:
- 策略模式:处理多规则。
- 工厂模式:封装对象创建。
- 模板方法:固定流程。
- 责任链:多步骤校验。
- 代理模式:日志、事务、权限增强。
- 适配器:对接第三方系统。
- 观察者:事件驱动后续动作。
真正理解设计模式,不是能说出它的 UML 图,而是能在业务代码变复杂前,找到合适的方式把变化隔离出去。
If this article helped you, please share it with others!
Some information may be outdated






