云计算百科
云计算领域专业知识百科平台

Java 开发中最常用的设计模式有哪些?结合实际开发情况一次讲清

很多人学设计模式时,背的是“23 种模式”;真正写项目时,却经常不知道该用哪一种。本文不按教材逐个复述定义,而是结合 Java 和 Spring 工程,聊聊那些真正高频的设计模式、应用场景,以及使用时容易踩的坑。

一、设计模式到底解决什么问题?

刚开始写 Java 项目时,我们更关心的是“功能能不能跑起来”。业务规模变大后,问题会逐渐变成:增加一种支付方式为什么要改一大片代码?多个流程都包含相同步骤,为什么每个类都要复制一遍?一个请求要经过校验、风控、日志和权限判断,怎样才能避免写出几十层 if-else?

设计模式解决的并不是语法问题,而是代码如何面对变化的问题。

它是前人在重复工程场景中总结出来的一组设计经验。合理使用后,代码通常会更容易扩展、复用和测试。但设计模式也不是越多越好。如果一个简单功能被拆成十几个接口和实现类,维护成本反而可能更高。

经典的 GoF 设计模式共有 23 种,通常分为三类:

  • 创建型模式:解决对象怎么创建,如单例、工厂、建造者模式;

  • 结构型模式:解决类和对象怎么组合,如代理、适配器、装饰器模式;

  • 行为型模式:解决对象之间怎么协作,如策略、模板方法、责任链、观察者模式。

实际 Java 工程中,我们没有必要为了“凑模式”而使用全部 23 种。下面重点介绍最常见、也最值得掌握的几种。

二、单例模式:保证全局只有一个实例

单例模式适合创建成本较高、需要统一管理,而且不应该重复创建的对象,例如配置中心客户端、线程池管理器和本地缓存管理器。

Java 中比较简洁、安全的写法是使用枚举:

public enum ConfigManager {
INSTANCE;

public String get(String key) {
return "config-value";
}
}

在 Spring 项目中,Bean 默认就是单例作用域,所以多数业务类不需要自己再手写“双重检查锁”。需要注意的是,Spring 单例只表示容器中通常只有一个 Bean 实例,并不代表它天然线程安全。如果成员变量保存了可变的请求数据,并发访问时仍可能出现数据覆盖。

三、工厂模式:把对象创建与业务使用分开

当系统存在多种实现,并且调用方不应该关心具体对象如何创建时,可以考虑工厂模式。

例如,一个订单系统同时支持支付宝、微信和银行卡支付。如果业务代码中到处出现:

if (type.equals("alipay")) {
return new AlipayService();
} else if (type.equals("wechat")) {
return new WechatPayService();
}

新增支付方式时,很多地方都可能被迫修改。更合适的方式是让工厂统一选择实现:

public interface PayService {
void pay(BigDecimal amount);
}

public class PayServiceFactory {
private final Map<String, PayService> serviceMap;

public PayServiceFactory(List<PayService> services) {
this.serviceMap = services.stream()
.collect(Collectors.toMap(PayService::type, s -> s));
}

public PayService get(String type) {
PayService service = serviceMap.get(type);
if (service == null) {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
return service;
}
}

在 Spring 中,经常通过“接口 + 多个 Bean + Map 注入”实现类似效果。工厂模式的价值不是少写一次 new,而是隔离创建规则,让业务层只依赖抽象。

四、策略模式:消灭不断膨胀的条件分支

策略模式和工厂模式经常一起使用。工厂负责找到对象,策略负责封装不同业务算法。

比如优惠计算可能包含满减、折扣、会员价和新人优惠。如果全部塞进一个方法,后续很容易演变成复杂的 if-else。可以把每种规则拆成独立策略:

public interface DiscountStrategy {
String type();
BigDecimal calculate(BigDecimal originPrice);
}

调用时根据类型选择对应策略即可。这样新增一种优惠规则通常只需要增加一个实现类,原有核心流程无需修改,更符合开闭原则。

但不是看到两个 if 就必须上策略模式。如果分支固定、逻辑很短、未来也不会扩展,直接判断反而更清楚。策略模式适合的是“变化频繁且各分支具有独立业务逻辑”的场景。

五、模板方法:流程固定,部分步骤允许变化

模板方法模式适合整体流程基本一致,但某些步骤需要由子类定制的场景。

以文件导入为例,不论导入 CSV、Excel 还是 JSON,主流程通常都是:读取文件、解析数据、校验、保存、记录结果。可以在抽象类中固定主流程,把解析步骤交给子类实现:

public abstract class AbstractImporter {

public final void importData(File file) {
checkFile(file);
List<?> data = parse(file);
validate(data);
save(data);
}

protected abstract List<?> parse(File file);
}

JDK 的 InputStream、Spring 的 JdbcTemplate、RedisTemplate 都体现了类似思想:框架控制稳定流程,开发者只提供变化部分。

模板方法依赖继承,流程层级多时可能不够灵活。如果变化需要自由组合,使用组合加策略模式往往更合适。

六、责任链模式:让请求依次经过多个处理节点

在实际后端项目中,一次请求经常需要经过参数校验、权限验证、风控、限流和业务处理。把所有逻辑写在一个方法里,会造成类越来越大,也很难调整执行顺序。

责任链模式把每个处理步骤封装成独立节点:当前节点处理完后,再决定是否交给下一个节点。常见场景包括:

  • Servlet Filter 和 Spring MVC Interceptor;

  • 网关鉴权、限流、黑名单检查;

  • 订单创建前的库存、价格、用户状态校验;

  • Agent 工具调用前的参数验证、权限检查和结果审核。

它的优势是节点可以独立增加、删除和排序。风险是链路过长后,排查问题时不容易看出请求在哪个节点被终止。因此工程中应记录统一的链路日志,并明确节点顺序。

七、观察者模式:一个事件触发多个后续动作

用户付款成功后,系统可能需要更新订单、扣减库存、增加积分并发送通知。如果支付服务直接调用所有下游模块,模块之间会形成很强的耦合。

观察者模式的思路是:支付服务只发布“支付成功事件”,由不同监听者分别处理后续逻辑。Spring 中可以通过 ApplicationEventPublisher 和 @EventListener 实现;分布式系统中,消息队列本质上也体现了事件发布与订阅思想。

不过,本地事件不等于可靠消息。进程突然退出时,尚未处理的事件可能丢失。涉及订单、资金等重要业务时,还需要结合事务消息、Outbox、重试和幂等机制,不能因为使用了观察者模式就忽略一致性问题。

八、代理模式:在不改核心逻辑的情况下增强功能

代理模式会在目标对象外增加一层,用于权限控制、日志、缓存、事务或远程调用。Spring AOP 是 Java 开发者最熟悉的应用之一:业务方法只关注业务,事务和日志等横切逻辑交给代理处理。

常见例子包括:

  • @Transactional 的事务代理;

  • MyBatis Mapper 接口的动态代理;

  • Feign 根据接口生成远程调用代理;

  • RPC 框架生成服务调用代理。

代理模式有一个高频坑:同一个类内部直接调用另一个带 @Transactional 的方法时,调用可能没有经过 Spring 代理,导致事务不生效。因此,使用框架提供的设计模式时,不仅要知道“用了什么”,还要理解它的调用边界。

九、实际工程中应该怎么选择?

设计模式没有固定答案,可以先从代码中的“变化点”出发:

工程问题可考虑的模式
对象创建复杂,调用方不应关心实现 工厂、建造者
多种算法可互相替换 策略
主流程固定,部分步骤不同 模板方法
请求要经过多个处理节点 责任链
一个事件需要通知多个模块 观察者
不修改业务代码就增加事务、日志等能力 代理、装饰器
需要适配不兼容的第三方接口 适配器

我的经验是,不要先问“这里能套什么模式”,而要先问三个问题:当前代码真正会变化的部分是什么?这种变化是否已经导致重复修改?引入接口和抽象后,维护成本真的会下降吗?

如果答案并不明确,先写简单、清楚的代码通常更合适。等变化真实出现,再重构成相应模式,也比一开始过度设计更稳妥。

十、总结

Java 设计模式不是面试时背诵的 23 个定义,而是一套处理工程变化的语言。单例管理唯一实例,工厂隔离对象创建,策略封装可替换规则,模板方法复用稳定流程,责任链拆分处理步骤,观察者实现事件解耦,代理统一增强横切能力。

真正掌握设计模式的标志,不是看到任何代码都能说出一个模式名称,而是能判断:这个问题是否值得引入模式,引入后解决了什么,又付出了怎样的复杂度。

在项目里,最好的设计往往不是模式最多的设计,而是业务发生变化时,修改范围依然可控的设计。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Java 开发中最常用的设计模式有哪些?结合实际开发情况一次讲清
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!