本文目录导读:

Java项目重构是一个非常经典且实用的话题,一个好的重构案例不仅能展示代码优化技巧,更能体现架构思维的演进。
下面我将从一个典型的“银行转账”业务入手,展示一个从面向过程 + 贫血模型逐步重构为领域驱动 + 设计模式的完整案例。
背景:一个“腐化”的转账服务
假设我们接手了一个老项目,核心功能是转账,最初的代码可能长这样:
// 这是最初版本的代码,常见于CRUD为主的系统
@Service
@Transactional
public class TransferServiceV1 {
@Autowired
private AccountMapper accountMapper; // MyBatis Mapper
@Autowired
private LedgerMapper ledgerMapper;
public String transfer(Long fromId, Long toId, BigDecimal amount) {
// 1. 校验
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
return "金额不合法";
}
Account from = accountMapper.selectById(fromId);
Account to = accountMapper.selectById(toId);
if (from == null || to == null) {
return "账户不存在";
}
if (from.getBalance().compareTo(amount) < 0) {
return "余额不足";
}
// 2. 业务逻辑(挂在Service层)
// 注意:这里的负数表示扣款,正数表示加款
BigDecimal newFromBalance = from.getBalance().subtract(amount);
BigDecimal newToBalance = to.getBalance().add(amount);
// 3. 更新数据库(散落的数据访问)
from.setBalance(newFromBalance);
to.setBalance(newToBalance);
accountMapper.updateById(from);
accountMapper.updateById(to);
// 4. 记账(硬编码)
Ledger ledger = new Ledger();
ledger.setFromId(fromId);
ledger.setToId(toId);
ledger.setAmount(amount);
ledger.setTime(new Date());
ledgerMapper.insert(ledger);
return "SUCCESS";
}
}
问题分析:
- 业务逻辑泄漏:核心业务逻辑(余额计算)放在了Service层,Account对象只是一个“数据袋子”。
- 事务边界模糊:如果更新账户成功但记账失败,事务需要回滚,但这里的代码顺序和异常处理不够健壮。
- 扩展性差:如果增加“手续费”、“汇率换算”或“风控校验”,只能不断往这个方法里堆代码。
- 可测试性差:难以脱离Spring和数据库进行单元测试。
重构目标
我们将分两个阶段进行重构:
- 组件化与异常处理 (提升可维护性)
- 引入领域模型与策略模式 (提升扩展性与架构清晰度)
组件化与异常处理
核心思路:把“查账户”、“更新账户”封装成独立的类,并定义业务异常。
// 1. 定义业务异常
public class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) { super(message); }
}
// 2. 将账户操作封装为AccountService(消除贫血模型的一部分)
@Service
public class AccountService {
private final AccountRepository accountRepository;
// 使用更通用的 Repository 替代 Mapper
public Account findAccount(Long id) {
return accountRepository.findById(id)
.orElseThrow(() -> new AccountNotFoundException("账户不存在: " + id));
}
// 扣款操作封装在领域服务中
public void debit(Account account, BigDecimal amount) {
account.debit(amount); // 把操作放回 Account 对象!
}
public void credit(Account account, BigDecimal amount) {
account.credit(amount);
accountRepository.save(account);
}
}
// 3. 重构后的转账服务(专注与流程编排)
@Service
@Transactional
public class TransferServiceV2 {
private final AccountService accountService;
private final LedgerService ledgerService;
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 1. 获取账户(通过Service)
Account from = accountService.findAccount(fromId);
Account to = accountService.findAccount(toId);
// 2. 执行扣款与加款(业务规则被移入领域模型)
from.withdraw(amount); // 内部会校验余额
to.deposit(amount);
// 3. 持久化(由Repository统一管理)
accountService.save(from);
accountService.save(to);
// 4. 记账
ledgerService.recordTransaction(fromId, toId, amount, "TRANSFER");
}
}
关键改进:
- 异常处理:定义了
InsufficientBalanceException,不需要返回字符串,由全局异常处理器统一处理。 - 领域模型苏醒:
Account类开始有行为,如withdraw()和deposit()。 - Repository模式:使用
AccountRepository取代 MyBatis Mapper,便于替换实现和测试。
引入设计模式与领域事件
核心思路:解决扩展性问题,现在要求 “跨行转账需要手续费” 或 “大额转账需要审批”,如果继续叠加if-else,代码会失控。
解决方案:使用 策略模式 处理费用,使用 模板方法模式 处理转账流程。
定义转账策略接口
public interface TransferPolicy {
// 计算手续费
BigDecimal calculateFee(Account from, Account to, BigDecimal amount);
// 是否需要额外审批
boolean requiresApproval(TransferRequest request);
}
实现具体策略
// 策略1:一般手续费
@Component
public class StandardTransferPolicy implements TransferPolicy {
@Override
public BigDecimal calculateFee(Account from, Account to, BigDecimal amount) {
return BigDecimal.ZERO; // 同行转账免费
}
@Override
public boolean requiresApproval(TransferRequest request) {
return false;
}
}
// 策略2:跨行转账(注意:这里通过@Qualifier或名称区分)
@Component("interBankPolicy")
public class InterBankTransferPolicy implements TransferPolicy {
private static final BigDecimal FEE_RATE = new BigDecimal("0.01");
@Override
public BigDecimal calculateFee(Account from, Account to, BigDecimal amount) {
// 假设不同的银行通过Account的bankCode区分
if (!from.getBankCode().equals(to.getBankCode())) {
return amount.multiply(FEE_RATE).setScale(2, RoundingMode.HALF_UP);
}
return BigDecimal.ZERO;
}
@Override
public boolean requiresApproval(TransferRequest request) {
// 跨行转账超5万需要审批
return request.getAmount().compareTo(new BigDecimal("50000")) > 0;
}
}
策略工厂(根据上下文选择策略)
@Component
public class TransferPolicyFactory {
private final List<TransferPolicy> strategies;
// Spring 自动注入所有策略实现
public TransferPolicyFactory(List<TransferPolicy> strategies) {
this.strategies = strategies;
}
public TransferPolicy getPolicy(Account from, Account to, BigDecimal amount) {
// 这里简单模拟:如果是跨行,则选用跨行策略,否则用标准策略
// 实际项目中可以用一个 Matcher 列表来判断
return strategies.stream()
.filter(p -> p instanceof InterBankTransferPolicy)
.findFirst()
.orElseGet(() -> strategies.get(0)); // 默认标准策略
}
}
引入领域事件(解耦“记账”和“通知”)
// 定义一个事件
public record TransferCompletedEvent(Long fromId, Long toId, BigDecimal amount) {}
改造V2版本,发布事件:
@Transactional
@Service
public class TransferServiceV3 {
private final AccountService accountService;
private final TransferPolicyFactory policyFactory;
private final ApplicationEventPublisher eventPublisher;
public void transfer(TransferRequest request) {
Long fromId = request.getFromId();
Long toId = request.getToId();
BigDecimal amount = request.getAmount();
// 1. 获取账户
Account from = accountService.findAccount(fromId);
Account to = accountService.findAccount(toId);
// 2. 策略选择
TransferPolicy policy = policyFactory.getPolicy(from, to, amount);
// 3. 计算手续费
BigDecimal fee = policy.calculateFee(from, to, amount);
// 4. 业务执行(考虑手续费)
from.withdraw(amount.add(fee)); // 扣本金+手续费
to.deposit(amount); // 对方到账本金
accountService.save(from);
accountService.save(to);
// 5. 回填手续费记录(创建账本条目)
ledgerService.recordTransaction(fromId, toId, amount, fee, "TRANSFER");
// 6. 发布事件(完成异步通知等)
eventPublisher.publishEvent(new TransferCompletedEvent(fromId, toId, amount));
}
}
总结对比
| 维度 | V1 (原始) | V2 (组件化) | V3 (领域驱动) |
|---|---|---|---|
| 代码风格 | 面向过程 | 面向对象(部分) | 领域驱动(DDD) |
| 职责划分 | Service完成所有事 | Repository + Service分层 | Service编排 + Domain行为 + Policy策略 |
| 扩展点 | add if-else | 修改原方法 | 新增策略类即可 |
| 测试难度 | 需要MockDB | 需要MockRepository | 可纯单元测试(Mock策略) |
| 业务表达 | 一串数字计算 | withdraw / deposit |
策略匹配 + 领域事件 |
最终建议: 重构不是一蹴而就的。每次重构只做一步,保持测试通过,上述案例展示了如何将“烂代码”逐步演进为干净、可维护、可扩展的系统,在实际项目中,你可以参考这个思路,从封装异常开始,逐步引入设计模式。