第5章:分层与泄漏(Layers and Leakage)
一、导读
1.1 本章学习目标
第5章讲解分层设计中的问题——抽象泄漏和层间通信。
本章的核心学习目标包括:
- 理解抽象泄漏的概念
- 掌握层间通信的规则
- 学会避免跨越调用
- 理解分层的实践策略
1.2 为什么本章如此重要
分层是信息隐藏的主要手段,但分层设计容易出现问题——下层细节泄漏到上层,或者上层绕过中间层直接调用下层。
1.3 核心问题引导
什么是抽象泄漏?为什么它有害?
层间通信应该遵循什么规则?
什么是跨越调用?如何避免?
分层应该分几层?
二、核心概念详解
2.1 抽象泄漏
抽象泄漏(Leaky Abstraction):
抽象的实现细节泄漏到了接口中
例:ORM 框架
// 理论上,ORM 隐藏了 SQL
User user = session.get(User.class, id);
// 但实际上,SQL 的细节泄漏了:
// - N+1 查询问题
// - 懒加载 vs 急加载
// - 缓存失效
// 用户仍然需要理解 SQL 才能高效使用
抽象泄漏不可避免,但可以最小化:
1. 选择好的抽象
2. 提供逃生舱口(escape hatch)
3. 文档化泄漏的部分2.2 层间通信规则
规则1:只能调用相邻层
应用层
↓
服务层
↓
数据层
应用层不能直接调用数据层
规则2:上层调用下层,下层不直接调用上层
上层 → 下层(正常调用)
下层 → 上层(通过回调/事件)
规则3:每层只暴露必要的接口
每层为上层提供清晰的接口
隐藏内部实现2.3 跨越调用
跨越调用(Pass-Through Method):
方法只是调用另一个方法,不做任何事
// 跨越调用
public class UserService {
private UserRepository repo;
public User findById(long id) {
return repo.findById(id); // 什么都没做
}
}
跨越调用的问题:
1. 增加了一层但没有增加价值
2. 增加认知负荷
3. 增加维护成本
解决方案:
1. 删除跨越层
2. 或者让这一层做一些有意义的事
// 有意义的层
public class UserService {
public User findById(long id) {
User user = repo.findById(id);
if (user == null) throw new NotFoundException();
auditLogger.logAccess(user); // 增加了审计功能
return user;
}
}2.4 分层策略
分多少层?
太少:
- 一层做太多事
- 难以理解和修改
太多:
- 跨越调用
- 性能开销
- 复杂性增加
经验法则:
- 3-5 层通常足够
- 每层应该有明确的职责
- 每层应该提供有意义的抽象
常见分层:
1. 接口层 → 应用层 → 领域层 → 基础设施层(DDD)
2. 表示层 → 业务层 → 数据层(三层架构)
3. 控制器 → 服务 → 仓储 → 数据库三、实战案例
3.1 案例一:修复分层问题
java
// 问题:跨越调用 + 抽象泄漏
public class OrderController {
private JdbcTemplate jdbc; // 泄漏了 JDBC
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable long id) {
return jdbc.queryForObject( // 跨越了服务层
"SELECT * FROM orders WHERE id = ?",
new Object[]{id},
new OrderMapper()
);
}
}
// 修复:正确的分层
public class OrderController {
private OrderService orderService; // 只依赖服务层
@GetMapping("/orders/{id}")
public OrderDTO getOrder(@PathVariable long id) {
Order order = orderService.findById(id); // 调用服务层
return OrderDTO.from(order);
}
}
public class OrderService {
private OrderRepository repository; // 只依赖仓储接口
public Order findById(OrderId id) {
Order order = repository.findById(id);
if (order == null) throw new NotFoundException();
return order;
}
}
public class OrderRepository {
// 隐藏了 JDBC 实现细节
}四、本章小结
抽象泄漏:实现细节泄漏到接口中,不可避免但可最小化。
层间通信:只调用相邻层,上层调用下层。
跨越调用:方法只转发调用,应删除或增加价值。
分层策略:3-5 层,每层有明确职责。
五、思考与练习
在你的代码中找出一个抽象泄漏的例子。
如何消除跨越调用?
你的项目分了几层?是否合理?