buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

这样构建对象,太帅了!—— 阶梯式Builder模式与代码整洁之道

在Java的世界里,优雅地构建一个复杂对象,就像精心冲泡一杯手冲咖啡——每一步都恰到好处,最终呈现完美的风味。

一、传统对象构建的痛点

想象一下,你需要构建一个记账请求对象,它有8个字段,其中一些是必填的,一些有依赖关系,一些有业务规则约束。传统的做法会让你面临什么困境?

1. 全参构造函数的灾难

// 灾难般的调用方式
new AccountingVO(accountNo, transOrderNo, transType, amount, 
                 whatBalanceDoes, accountingType, unfreezeAccountingType, 
                 memo, operator);
  • 🔢 参数顺序容易出错:8个参数,你能记住正确顺序吗?
  • 可读性极差:调用时完全看不出每个参数代表什么
  • 🚫 无法处理可选参数:所有参数都必须提供

2. Setter方法的隐患

AccountingVO vo = new AccountingVO();
vo.setAccountNo(accountNo);
vo.setTransOrderNo(transOrderNo);
// ... 省略6个setter
vo.setOperator(operator);
  • ⚠️ 对象状态不确定:在setter调用完成前,对象处于无效状态
  • 🔍 容易遗漏必填字段:没有编译时检查
  • 🔗 无法保证字段依赖关系:比如必须先设置账户号才能设置交易信息

二、阶梯式建造者模式:优雅的解决方案

让我们看看示例代码中展示的精妙设计。这不是普通的建造者模式,而是带有强制顺序的阶梯式建造者模式

设计精妙之处

1. 清晰的构建步骤接口

// 定义明确的构建步骤
public interface BuildSteps {
    interface AccountStep {
        TransOrderStep withAccountNo(String accountNo);  // 第一步:设置账户
    }
    
    interface TransOrderStep {
        AccountingStep withTransOrderNo(String transOrderNo, AcTransTypeEnum transType);  // 第二步:设置交易
    }
    
    // ... 更多步骤
}

每个步骤接口只暴露当前步骤可用的方法,强制调用者按照正确的顺序构建对象

2. 流畅的API调用体验

// 像说故事一样构建对象
AccountingVO vo = AccountingVO.builder()
    .withAccountNo("ACC001")                    // 第一步:必须有账户
    .withTransOrderNo("TXN2025001", DEBIT)     // 第二步:必须有交易
    .withAccounting(amount, INCOME, BALANCE_UP) // 第三步:记账逻辑
    .withOperation("张三", "工资收入")          // 第四步:操作信息
    .build();                                   // 最终构建

3. 处理复杂业务场景

代码中特别处理了解冻并扣减这种复杂场景:

// 专门的方法处理特定业务逻辑
.withUnfreezeAndDownAccounting(
    amount, 
    UNFREEZE_ACCOUNTING, 
    DEDUCTION_ACCOUNTING
)

这种方法不仅提供了便捷性,更封装了业务知识,确保调用者不会用错参数组合。

三、为什么这种设计"太帅了"?

1. 🛡️ 编译时安全性

错误的构建顺序会在编译时报错:

// 编译错误!必须先设置账户才能设置交易
AccountingVO.builder()
    .withTransOrderNo("TXN001", DEBIT)  // ❌ 编译错误
    .withAccountNo("ACC001")
    .build();

2. 📖 自文档化的API

方法名清晰表达了意图,不需要额外注释:

// 读起来就像业务需求文档
.withAccounting(amount, INCOME, BALANCE_UP)  // 记账:收入,余额增加
.withOperation("张三", "工资发放")            // 操作人:张三,备注:工资发放

3. 🔒 不可变对象的优雅构建

虽然示例中VO是可变的(使用了setter),但我们可以轻松改造为不可变对象:

// 结合建造者模式创建不可变对象
@Getter
@ToString
public class AccountingVO {
    private final String accountNo;
    private final String transOrderNo;
    // ... 所有字段都是final
    
    // 私有构造函数,只能通过建造者创建
    private AccountingVO(AccountVOBuilder builder) {
        this.accountNo = builder.accountNo;
        this.transOrderNo = builder.transOrderNo;
        // ...
    }
}

4. 🧩 灵活应对变化

当需要增加新字段时,只需在建造者中添加新步骤,不影响现有调用:

// 新增一个渠道字段
public interface BuildSteps {
    // 在适当的位置插入新步骤
    interface ChannelStep {
        BuildStep withChannel(String channel);
    }
}

四、实战应用场景

场景1:金融交易系统

// 构建一个转账请求
AccountingVO transfer = AccountingVO.builder()
    .withAccountNo("6225880123456789")          // 付款账户
    .withTransOrderNo("TRANS2025001", TRANSFER) // 转账交易
    .withAccounting(
        Money.of(1000.00, "CNY"),               // 转账金额
        EXPENSE,                                // 支出类型
        BALANCE_DOWN                            // 余额减少
    )
    .withOperation("系统", "向用户B转账")        // 操作信息
    .build();

场景2:复杂业务组合

// 处理解冻并扣减的复杂场景
AccountingVO unfreeze = AccountingVO.builder()
    .withAccountNo("ACC002")
    .withTransOrderNo("UNFREEZE001", SETTLEMENT)
    .withUnfreezeAndDownAccounting(            // 专门处理解冻扣减
        Money.of(500.00, "CNY"),
        UNFREEZE_ACCOUNTING,                   // 先解冻
        DEDUCTION_ACCOUNTING                   // 再扣减
    )
    .withOperation("风控系统", "风险解冻扣款")
    .build();

五、实现要点与最佳实践

1. 保持VO的简洁性

// VO本身应该保持简单
@Getter
@ToString
public class AccountingVO {
    // 只有字段和getter,没有setter
    private String accountNo;
    private String transOrderNo;
    // ...
    
    // 私有构造函数
    private AccountingVO() {}
    
    // 公开的建造者入口
    public static BuildSteps.AccountStep builder() {
        return new AccountVOBuilder();
    }
}

2. 建造者的内部实现

// 建造者内部类实现所有步骤接口
private static class AccountVOBuilder 
    implements BuildSteps.AccountStep, 
               BuildSteps.TransOrderStep,
               BuildSteps.AccountingStep,
               BuildSteps.OperationStep,
               BuildSteps.BuildStep {
    
    private final AccountingVO accountingVO;
    
    private AccountVOBuilder() {
        this.accountingVO = new AccountingVO();
    }
    
    @Override
    public BuildSteps.TransOrderStep withAccountNo(String accountNo) {
        this.accountingVO.accountNo = accountNo;
        return this;  // 返回下一步的接口
    }
    
    // ... 其他实现
}

3. 验证逻辑的放置

可以在build()方法中添加验证:

@Override
public AccountingVO build() {
    // 构建前的验证
    validate();
    return this.accountingVO;
}

private void validate() {
    if (accountingVO.accountNo == null) {
        throw new IllegalStateException("账户号不能为空");
    }
    if (accountingVO.transOrderNo == null) {
        throw new IllegalStateException("交易订单号不能为空");
    }
    // ... 更多验证
}

六、与其他模式的对比

模式 优点 缺点 适用场景
全参构造函数 简单直接 参数多时难以使用 字段少于4个的简单对象
Setter方法 灵活,可分步设置 对象状态不确定,线程不安全 需要频繁修改的对象
传统建造者 可读性好,支持可选参数 无法强制构建顺序 大多数复杂对象构建
阶梯式建造者 强制正确顺序,编译时安全 实现稍复杂 有严格构建顺序的业务对象

七、何时使用这种模式?

✅ 推荐使用场景:

  1. 字段多且有依赖关系的对象(如必须先有A才能有B)
  2. 业务规则复杂,需要确保构建顺序
  3. API需要自文档化,提高代码可读性
  4. 团队协作,减少误用可能

❌ 不推荐使用场景:

  1. 字段少于3个的简单对象
  2. 没有构建顺序要求的场景
  3. 性能极其敏感的场景(有轻微开销)

结语:优雅代码的艺术

这种阶梯式建造者模式不仅仅是一种技术实现,更是一种设计哲学的体现。它告诉我们:

  1. 优秀的API设计应该引导用户正确使用,而不是依赖文档或记忆
  2. 编译时检查比运行时检查更可靠
  3. 代码应该表达业务意图,而不仅仅是技术实现

同时,这个阶梯式Builder模式,更是整洁代码理念的完美体现:

  1. 它尊重读者的智慧:通过清晰的命名和结构,让代码不言自明
  2. 它尊重维护者的时间:通过编译时检查和良好的封装,减少调试时间
  3. 它尊重业务的变化:通过灵活的设计,优雅地应对需求变更
  4. 它尊重团队的合作:通过一致的约定,降低沟通成本

在《代码整洁之道》中,Robert C. Martin说:"整洁的代码只做好一件事。" 这个Builder模式正是如此——它专注于一件事:以最优雅、最安全的方式构建对象。

当你下次需要构建一个复杂对象时,不妨想想这个"太帅了"的设计。它可能多写了几行代码,但带来的可维护性、安全性和开发体验的提升,绝对是值得的。

记住:好的代码不是让机器容易理解,而是让人容易理解。 当你的代码既能正确运行,又能清晰表达业务意图时,你也许就真正掌握了整洁代码的艺术。


"编程的艺术在于创造简单,而非忍受复杂。"


※如需要AccountingVO完整代码,可私聊,或通过微信公众号《靠谱的程序员》找到我。※

posted on 2026-02-26 15:09  buguge  阅读(0)  评论(0)    收藏  举报