Java项目重构案例

wen java案例 1

本文目录导读:

Java项目重构案例

  1. 背景:一个“腐化”的转账服务
  2. 重构目标
  3. 阶段一:组件化与异常处理
  4. 阶段二:引入设计模式与领域事件
  5. 总结对比

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";
    }
}

问题分析:

  1. 业务逻辑泄漏:核心业务逻辑(余额计算)放在了Service层,Account对象只是一个“数据袋子”。
  2. 事务边界模糊:如果更新账户成功但记账失败,事务需要回滚,但这里的代码顺序和异常处理不够健壮。
  3. 扩展性差:如果增加“手续费”、“汇率换算”或“风控校验”,只能不断往这个方法里堆代码。
  4. 可测试性差:难以脱离Spring和数据库进行单元测试。

重构目标

我们将分两个阶段进行重构:

  1. 组件化与异常处理 (提升可维护性)
  2. 引入领域模型与策略模式 (提升扩展性与架构清晰度)

组件化与异常处理

核心思路:把“查账户”、“更新账户”封装成独立的类,并定义业务异常。

// 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 策略匹配 + 领域事件

最终建议: 重构不是一蹴而就的。每次重构只做一步,保持测试通过,上述案例展示了如何将“烂代码”逐步演进为干净、可维护、可扩展的系统,在实际项目中,你可以参考这个思路,从封装异常开始,逐步引入设计模式。

抱歉,评论功能暂时关闭!