前言
刚开始学习设计模式时,我经常有一种感觉:明明几行代码就能完成的功能,套上设计模式后反而多出了接口、实现类和工厂类。
后来做项目才慢慢明白,设计模式不是为了把代码写得"高级",而是为了解决代码变化后越来越难维护的问题。对象参数多了怎么办?支付方式不断增加怎么办?调用一个功能要协调五个系统怎么办?这些才是设计模式真正要处理的事情。
本文挑选五种 Java 项目中经常见到的模式,用简单场景讲清楚它们分别解决什么问题。
一、建造者模式:把复杂对象一步步组装出来
假设要创建一个用户对象,它有姓名、年龄、手机号、邮箱和地址等字段。如果全部放进构造函数,代码可能变成这样:
User user = new User("张三", 25, "13800000000", null, "杭州");
只看调用代码,很难立刻知道每个参数代表什么。中间还有一个 null,参数顺序一旦写错,编译器也未必能发现。
建造者模式让对象通过链式调用逐步完成组装:
User user = User.builder()
.name("张三")
.age(25)
.phone("13800000000")
.address("杭州")
.build();
实现思路是在 User 内部提供一个 Builder:
public class User {
private String name;
private int age;
private String phone;
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.phone = builder.phone;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private int age;
private String phone;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}
不过,只有两三个字段的简单对象没必要强行使用 Builder。设计模式的目的是降低理解成本,不是增加文件数量。
二、工厂模式:把选择具体实现的工作集中起来
假设系统支持支付宝和微信支付。最直接的写法是在业务代码里判断:
if ("alipay".equals(type)) {
new AlipayService().pay();
} else if ("wechat".equals(type)) {
new WechatPayService().pay();
}
当支付渠道不断增加,这段判断会散落在多个地方。工厂模式的做法是把"创建哪一个对象"集中到工厂中。
public interface PayService {
void pay();
}
public class PayFactory {
public static PayService create(String type) {
return switch (type) {
case "alipay" -> new AlipayService();
case "wechat" -> new WechatPayService();
default -> throw new IllegalArgumentException("不支持的支付类型");
};
}
}
调用方只关心统一接口:
PayService payService = PayFactory.create(type);
payService.pay();
这里展示的是最容易理解的简单工厂。工厂方法会让每一种产品拥有自己的工厂,抽象工厂则用于创建一整组相关对象。核心目的:让调用方依赖抽象,并把具体对象的选择和创建集中管理。
在 Spring 项目中,我们也经常把多个实现注入为 Map<String, PayService>,再按类型取得对应 Bean。写法与传统工厂不同,但"隔离具体实现选择"的思路很接近。
三、单例模式:整个程序只保留一个实例
有些对象没有必要反复创建,例如配置管理器、注册表或本地缓存管理器。单例模式保证一个类在当前进程中只有一个实例,并提供统一访问入口。
比较简洁且线程安全的写法是静态内部类:
public class ConfigManager {
private ConfigManager() {
}
private static class Holder {
private static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return Holder.INSTANCE;
}
}
只有第一次调用 getInstance() 时,内部类才会被加载,JVM 的类初始化机制又能保证线程安全,因此不需要手动加锁。
需要注意,单例只保证对象数量,不代表对象内部天然线程安全。如果单例中保存可变的 ArrayList、计数器等共享状态,仍然要处理并发访问。
Spring 默认的 Singleton Bean 表示每个 IoC 容器中只有一份 Bean,生命周期由容器管理;它与手写的全局单例概念相似,但范围和创建方式并不完全相同。
四、外观模式:给复杂系统提供一个简单入口
一次下单可能需要依次完成库存检查、支付、积分增加和物流创建。如果 Controller 直接调用所有服务,它就会知道太多业务细节:
stockService.check();
payService.pay();
pointService.add();
deliveryService.create();
外观模式会增加一个统一入口,把这组协作过程封装起来:
public class OrderFacade {
private final StockService stockService;
private final PayService payService;
private final DeliveryService deliveryService;
public void submit(Order order) {
stockService.check(order);
payService.pay(order);
deliveryService.create(order);
}
}
调用方只需要:
orderFacade.submit(order);
外观模式没有改变底层服务的功能,它只是为复杂子系统提供更简单、稳定的入口。项目中的应用服务、聚合 Service,经常承担类似角色。
但外观类也不能变成什么都做的"万能类"。它适合组织调用流程,库存、支付等具体业务规则仍应留在各自服务中。
五、代理模式:在不改原对象的情况下控制调用
假设已有一个订单服务,现在希望在调用前后记录日志。如果直接修改业务类,日志代码会和核心逻辑混在一起。代理模式可以在外面增加一层:
public interface OrderService {
void createOrder();
}
public class OrderServiceProxy implements OrderService {
private final OrderService target;
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void createOrder() {
System.out.println("开始调用");
target.createOrder();
System.out.println("调用结束");
}
}
调用者面对的仍然是 OrderService,但实际请求先经过代理。权限校验、事务、日志、缓存和远程调用,都可能使用类似思路。
手动编写的是静态代理。JDK 动态代理可以在运行时为接口生成代理对象;CGLIB 则主要通过创建目标类的子类实现代理。Spring AOP 会根据场景选择代理方式,@Transactional 能在方法调用前后开启、提交或回滚事务,背后就离不开代理。
MyBatis 的 Mapper 接口没有实现类却可以直接调用,也是动态代理的典型应用:代理接住接口方法,再找到对应 SQL 执行。
代理与装饰器的结构很像,但侧重点不同。代理更关注控制访问,例如权限、事务或远程调用;装饰器更关注给对象叠加新的业务能力。
六、五种模式放在一起怎么区分
| 模式 | 主要解决的问题 | 一句话记忆 |
|---|---|---|
| 建造者模式 | 复杂对象怎样创建 | 一步步组装对象 |
| 工厂模式 | 应该创建哪种实现 | 把选择对象交给工厂 |
| 单例模式 | 对象需要多少份 | 当前范围只保留一个实例 |
| 外观模式 | 复杂系统怎样调用 | 对外提供统一简单入口 |
| 代理模式 | 调用过程怎样控制 | 在真实对象外增加一层 |
建造者和工厂都与创建对象有关,但建造者关注一个复杂对象如何组装,工厂关注创建哪一种对象。外观和代理都像"中间层",但外观在简化多个子系统的调用,代理则通常围绕某个目标对象控制或增强访问。