如何手写一个外观模式?Java 代码怎么实现?
简化版
手写外观模式就是定义一个 Facade 类,把多个子系统注入进来,对外暴露少量粗粒度方法,内部按顺序调用子系统完成复杂流程。客户端只依赖 Facade,不直接依赖多个子系统。
详细版
示例结构:
class OrderFacade {
private final UserService userService;
private final StockService stockService;
private final OrderService orderService;
OrderResult createOrder(CreateOrderRequest request) {
userService.check(request.userId());
stockService.lock(request.items());
return orderService.create(request);
}
}
客户端:
OrderResult result = orderFacade.createOrder(request);
实现重点:
- Facade 对外接口要少而清晰;
- 子系统仍负责具体能力;
- Facade 负责流程编排和结果聚合;
- 不要把所有业务逻辑都塞进 Facade;
- 复杂事务、幂等、异常处理要有明确策略。
完整版教学
一、先准备几个子系统
class UserService {
void checkUser(Long userId) {
System.out.println("校验用户");
}
}
class StockService {
void lockStock(List<Long> itemIds) {
System.out.println("锁定库存");
}
}
class OrderService {
Long createOrder(CreateOrderRequest request) {
System.out.println("创建订单");
return 1001L;
}
}
这些子系统分别负责自己的能力。
二、定义外观类
class OrderFacade {
private final UserService userService;
private final StockService stockService;
private final OrderService orderService;
OrderFacade(UserService userService,
StockService stockService,
OrderService orderService) {
this.userService = userService;
this.stockService = stockService;
this.orderService = orderService;
}
Long createOrder(CreateOrderRequest request) {
userService.checkUser(request.getUserId());
stockService.lockStock(request.getItemIds());
return orderService.createOrder(request);
}
}
Facade 对外暴露的是一个业务入口,内部隐藏了子系统调用顺序。
三、客户端只调用外观
OrderFacade facade = new OrderFacade(
new UserService(),
new StockService(),
new OrderService()
);
Long orderId = facade.createOrder(request);
客户端不需要知道创建订单前要先校验用户、再锁库存。
这降低了客户端复杂度。
四、Spring 项目中的写法
实际项目里通常这样写:
@Service
public class OrderFacade {
private final UserService userService;
private final StockService stockService;
private final OrderService orderService;
public OrderFacade(UserService userService,
StockService stockService,
OrderService orderService) {
this.userService = userService;
this.stockService = stockService;
this.orderService = orderService;
}
public OrderResult createOrder(CreateOrderCommand command) {
userService.check(command.userId());
stockService.lock(command.items());
return orderService.create(command);
}
}
Facade 常常位于 Controller 和多个领域服务之间。
五、外观类里应该写什么
Facade 适合放:
- 参数转换;
- 子系统调用顺序;
- 结果聚合;
- 统一异常转换;
- 事务边界;
- 调用日志;
- 权限校验入口。
但复杂业务规则最好放在领域服务或子系统里,不要把 Facade 写成全能类。
六、常见误区与追问
实现质量要从用例原子性和依赖方向检查。一次创建订单涉及校验、扣库存、落单3步,Facade 应清楚定义哪一步失败会回滚、哪一步可重试,而不是只顺序调用三个方法。客户端只依赖 Facade,子系统之间也不应为了这个入口反向依赖 Facade。
| 检查维度 | 判定依据 |
|---|---|
| Facade | 编排步骤、定义事务和返回契约 |
| Subsystem | 保留自身能力与业务不变量 |
易错点:把三个调用放进一个方法只是起点,失败边界和结果契约才决定实现是否可用。
- 误区:Facade 必须是单例。 外观模式与实例数量无关,依赖注入容器可按实际生命周期管理。
- 追问:事务注解应该放哪里? 单体应用中可放用例级 Facade,但要避免把远程调用包进长数据库事务。
- 误区:Facade 可以捕获并吞掉所有异常。 应转换为稳定的用例错误,同时保留日志和可诊断原因。
- 追问:返回底层实体合适吗? 对外更适合稳定 DTO,避免调用方依赖子系统内部模型。
- 追问:如何写实现测试? 验证3步调用顺序、失败短路、事务结果和对外错误映射。
七、加强记忆
手写外观模式就是写一个统一入口类,让客户端调用它,它再调用多个子系统完成复杂流程。Facade 负责收口和编排,子系统负责真实能力;边界清楚,代码才不会变成大杂烩。