一、为什么需要建造者模式?——从痛点出发
1. 问题场景:当对象像"乐高积木"需要组合时
想象你正在组装一台高配置电脑:
- 需要选择CPU(i7/i9)
- 需要搭配显卡(RTX 3080/4090)
- 需要确定内存大小(16G/32G)
- 还要选择硬盘(SSD 512G/1T)
如果用传统方式创建对象,代码会变成这样:
java
代码解读
复制代码
Computer computer = new Computer("i9-13900K", "RTX 4090", 64, "2TB SSD", ...);
当参数超过5个时,你还能分清哪个参数对应哪个部件吗?
2. 传统方式的致命缺陷(附代码对比)
致命问题一:参数顺序噩梦
java
代码解读
复制代码
// 两个构造方法参数顺序不同,但编译不会报错! new User("张三", 25); // (name, age) new User(25, "张三"); // (age, name) ❌
致命问题二:参数爆炸的构造方法
java
代码解读
复制代码
// 参数超过5个时,调用者会陷入参数地狱 new Order( "ORD20230901001", // 订单号 "张三", // 用户姓名 "13800138000", // 手机号 "北京市海淀区", // 地址 new Date(), // 创建时间 2, // 商品数量 1999.00, // 总金额 "满减优惠", // 优惠类型 "顺丰快递" // 物流方式 );
致命问题三:Setter的定时炸弹
java
代码解读
复制代码
User user = new User(); user.setName("李四"); // 忘记设置必填项age user.setAge(null); // 不小心设置错误类型 // 此时user对象处于不完整状态!
3. 建造者模式的救赎(代码对比)
解决方案一:分步组装就像点麦当劳套餐
java
代码解读
复制代码
Computer computer = new ComputerBuilder() .chooseCpu("i9-13900K") // 第一步:选CPU .addGpu("RTX 4090") // 第二步:加显卡 .setRam(64) // 第三步:定内存 .withStorage("2TB SSD") // 第四步:配硬盘 .build(); // 最后组装成品
解决方案二:强制完成必填项
java
代码解读
复制代码
public User build() { if (name == null || age == null) { throw new IllegalStateException("姓名和年龄是必填项!"); } return new User(name, age, phone...); }
解决方案三:链式调用清晰如对话
java
代码解读
复制代码
// 读代码就像读说明书 HttpRequest request = new HttpRequestBuilder() .method("POST") .url("https://api.example.com") .header("Content-Type", "application/json") .body("{'key':'value'}") .timeout(5000) .build();
真实项目中的惨痛教训:某电商系统订单创建BUG
某电商平台曾因订单参数顺序错误导致:
- 用户地址被存入商品数量字段
- 物流方式变成浮点数
- 引发大规模订单数据混乱
改用建造者模式后:
java
代码解读
复制代码
// 明确每个参数的语义 Order order = new OrderBuilder() .orderId("ORD20230901001") .userName("张三") .shippingAddress("北京市海淀区") .itemCount(2) .totalAmount(1999.00) .build();
从此再未出现参数错位问题!
建造者模式的本质:把复杂对象的构建过程与最终表示分离,就像汽车工厂的流水线,每个工位只负责一个部件的安装,最后质检员(build()方法)确保整车完整可用。
二、建造者模式核心角色(附UML图)
核心角色交互图
构建
实现
使用
Computer
-cpu: String
-gpu: String
-ram: int
+Computer()
+getCpu() : String
+getGpu() : String
+getRam() : int
«interface»
ComputerBuilder
+setCpu(String cpu) : ComputerBuilder
+setGpu(String gpu) : ComputerBuilder
+setRam(int ram) : ComputerBuilder
+build() : Computer
StandardComputerBuilder
-computer: Computer
+setCpu(String cpu) : ComputerBuilder
+setGpu(String gpu) : ComputerBuilder
+setRam(int ram) : ComputerBuilder
+build() : Computer
ComputerDirector
-builder: ComputerBuilder
+constructGamingPC() : Computer
+constructOfficePC() : Computer
角色详解 + 代码对照
1. 产品(Product)——你的目标对象
java
代码解读
复制代码
// 就像你要组装的电脑整机 public class Computer { private String cpu; private String gpu; private int ram; // 全参构造器(通常设置为私有) public Computer(String cpu, String gpu, int ram) { this.cpu = cpu; this.gpu = gpu; this.ram = ram; } // Getter方法省略... }
2. 建造者(Builder)——组装说明书
java
代码解读
复制代码
// 定义组装步骤的接口 public interface ComputerBuilder { // 每个方法都返回Builder自身,实现链式调用 ComputerBuilder setCpu(String cpu); ComputerBuilder setGpu(String gpu); ComputerBuilder setRam(int ram); // 最终生成产品的方法 Computer build(); }
3. 具体建造者(ConcreteBuilder)——真正的组装工人
java
代码解读
复制代码
public class StandardComputerBuilder implements ComputerBuilder { private String cpu; private String gpu; private int ram; @Override public ComputerBuilder setCpu(String cpu) { this.cpu = cpu; return this; // 返回当前对象,实现链式调用 } // 其他set方法同理... @Override public Computer build() { // 此处可添加校验逻辑 if(cpu == null || gpu == null){ throw new IllegalArgumentException("CPU和显卡是必选配件!"); } return new Computer(cpu, gpu, ram); } }
4. 指挥者(Director)——流水线经理(可选)
java
代码解读
复制代码
public class ComputerDirector { private final ComputerBuilder builder; public ComputerDirector(ComputerBuilder builder) { this.builder = builder; } // 封装高端游戏机的配置流程 public Computer constructGamingPC() { return builder .setCpu("i9-13900K") .setGpu("RTX 4090") .setRam(64) .build(); } // 封装办公电脑的配置流程 public Computer constructOfficePC() { return builder .setCpu("i5-12400") .setGpu("Intel UHD 730") .setRam(16) .build(); } }
指挥者的使用场景演示
java
代码解读
复制代码
// 当需要预定义配置方案时 ComputerBuilder builder = new StandardComputerBuilder(); ComputerDirector director = new ComputerDirector(builder); // 快速获得预定义的两种配置 Computer gamingPC = director.constructGamingPC(); Computer officePC = director.constructOfficePC(); // 当需要自定义配置时,依然可以直接使用Builder Computer customPC = new StandardComputerBuilder() .setCpu("Ryzen 7 7800X") .setGpu("RX 7900 XTX") .setRam(32) .build();
架构师决策点:什么时候需要指挥者?
| 场景特征 | 使用指挥者 | 直接使用Builder |
|---|---|---|
| 需要固定配置模板 | ✅ | ❌ |
| 参数组合经常变化 | ❌ | ✅ |
| 多个子系统需要相同配置 | ✅ | ❌ |
| 希望隐藏复杂构建细节 | ✅ | ❌ |
设计建议:80%的场景不需要指挥者,但当你的系统出现重复的构建代码块时,就是引入Director的最佳时机。
三、经典应用场景举例(代码级详解)
1. 电商订单系统(不同优惠组合)
痛点:满减、折扣、赠品等多种优惠组合的灵活配置
java
代码解读
复制代码
// 产品类:订单 public class Order { private String orderId; // 订单号(必填) private String userId; // 用户ID(必填) private BigDecimal amount; // 订单金额(必填) private String coupon; // 优惠券(可选) private String gift; // 赠品(可选) // 其他10+个字段... // 私有构造器(只能通过Builder创建) private Order(OrderBuilder builder) { this.orderId = builder.orderId; this.userId = builder.userId; this.amount = builder.amount; this.coupon = builder.coupon; this.gift = builder.gift; } // 建造者(内部类) public static class OrderBuilder { // 必填字段通过构造函数传入 public OrderBuilder(String orderId, String userId, BigDecimal amount) { this.orderId = orderId; this.userId = userId; this.amount = amount; } // 可选字段通过链式方法设置 public OrderBuilder withCoupon(String coupon) { this.coupon = coupon; return this; } public OrderBuilder addGift(String gift) { this.gift = gift; return this; } public Order build() { validate(); // 校验必填字段 return new Order(this); } private void validate() { if (orderId == null || userId == null || amount == null) { throw new IllegalArgumentException("必填字段缺失"); } } } } // 使用示例:组合优惠订单 Order order = new Order.OrderBuilder("20230901001", "user123", new BigDecimal("299.00")) .withCoupon("DISCOUNT_20") // 添加折扣券 .addGift("保温杯") // 添加赠品 .build();
2. 游戏角色创建(可视化配置)
痛点:发型、肤色、装备等20+种可定制属性
java
代码解读
复制代码
// 产品类:游戏角色 public class GameCharacter { private String name; // 角色名 private String hairStyle; // 发型(默认:短发) private String armor; // 护甲(默认:布衣) private String weapon; // 武器(默认:木剑) // 建造者(与产品强关联) public static class Builder { private final GameCharacter character = new GameCharacter(); public Builder(String name) { // 必须指定角色名 character.name = name; character.hairStyle = "短发"; // 默认值 character.armor = "布衣"; character.weapon = "木剑"; } public Builder hairStyle(String style) { character.hairStyle = style; return this; } public Builder armor(String armor) { character.armor = armor; return this; } public Builder weapon(String weapon) { character.weapon = weapon; return this; } public GameCharacter build() { return character; // 直接返回已配置对象 } } } // 使用示例:创建豪华装备角色 GameCharacter warrior = new GameCharacter.Builder("战神") .hairStyle("金色长发") .armor("龙鳞铠甲") .weapon("屠龙宝刀") .build();
3. 报表生成器(分步配置)
痛点:表头、数据源、样式配置的复杂组合
java
代码解读
复制代码
// 产品类:Excel报表 public class ExcelReport { private List<String> headers; private List<List<Object>> data; private String style; private boolean autoSize; private ExcelReport() {} // 强制使用建造者 // 建造者 public static class ReportBuilder { private final ExcelReport report = new ExcelReport(); public ReportBuilder setHeaders(String... headers) { report.headers = Arrays.asList(headers); return this; } public ReportBuilder addDataRow(List<Object> row) { if (report.data == null) { report.data = new ArrayList<>(); } report.data.add(row); return this; } public ReportBuilder useModernStyle() { report.style = "现代风格"; report.autoSize = true; return this; } public ExcelReport build() { if (report.headers == null || report.data == null) { throw new IllegalStateException("缺少表头或数据"); } return report; } } } // 使用示例:销售报表 ExcelReport report = new ExcelReport.ReportBuilder() .setHeaders("日期", "产品", "销售额") .addDataRow(Arrays.asList("2023-09-01", "手机", 500000)) .addDataRow(Arrays.asList("2023-09-01", "笔记本", 300000)) .useModernStyle() .build();
4. HTTP请求构造(不可变对象)
痛点:Header、Body、超时时间等参数的灵活组合
java
代码解读
复制代码
// 产品类:HTTP请求(不可变) public final class HttpRequest { private final String method; private final String url; private final Map<String, String> headers; private final String body; private final int timeout; // 私有构造器 private HttpRequest(Builder builder) { this.method = builder.method; this.url = builder.url; this.headers = Collections.unmodifiableMap(builder.headers); this.body = builder.body; this.timeout = builder.timeout; } // 建造者 public static class Builder { private String method = "GET"; // 默认值 private String url; private Map<String, String> headers = new HashMap<>(); private String body; private int timeout = 5000; // 默认5秒 public Builder(String url) { // 必须参数 this.url = url; } public Builder method(String method) { this.method = method.toUpperCase(); return this; } public Builder addHeader(String key, String value) { headers.put(key, value); return this; } public Builder body(String body) { this.body = body; return this; } public Builder timeout(int timeout) { this.timeout = timeout; return this; } public HttpRequest build() { if (url == null) throw new IllegalArgumentException("URL不能为空"); if ("POST".equals(method) && body == null) { throw new IllegalStateException("POST请求必须包含Body"); } return new HttpRequest(this); } } } // 使用示例:构造POST请求 HttpRequest request = new HttpRequest.Builder("https://api.example.com/data") .method("POST") .addHeader("Content-Type", "application/json") .body("{\"key\":\"value\"}") .timeout(10000) .build();
场景共性总结
| 场景特点 | 建造者模式的解决方案 |
|---|---|
| 参数组合多变 | 通过链式调用自由组合 |
| 必填/选填参数混杂 | 强制在构造器中设置必填项 |
| 需要参数校验 | 在build()方法集中校验逻辑 |
| 创建过程需要明确语义 | 方法命名体现参数含义(如withXxx) |
| 需要构建不可变对象 | 通过final属性和unmodifiable集合实现 |
选择建议:当遇到"参数超过4个且存在多种组合方式"的情况,就该考虑建造者模式了!
四、手把手代码实现(含完整Java代码)
案例:电商平台优惠券系统
场景需求:
设计一个支持多种优惠券组合的订单系统,要求:
- 必须包含订单ID、用户ID、总金额
- 可选添加优惠券、赠品、备注
- 金额必须大于0,优惠券需校验有效期
- 保证订单对象的不可变性
第一步:定义产品类(不可变订单)
java
代码解读
复制代码
import java.math.BigDecimal; import java.time.LocalDate; // 不可变订单类 public final class CouponOrder { // 必填字段(final修饰) private final String orderId; private final String userId; private final BigDecimal totalAmount; // 可选字段(final修饰) private final String couponCode; private final String gift; private final String remark; // 私有构造器(只能通过Builder创建) private CouponOrder(Builder builder) { this.orderId = builder.orderId; this.userId = builder.userId; this.totalAmount = builder.totalAmount; this.couponCode = builder.couponCode; this.gift = builder.gift; this.remark = builder.remark; } // Getter方法(无Setter) public String getOrderId() { return orderId; } public String getUserId() { return userId; } public BigDecimal getTotalAmount() { return totalAmount; } public String getCouponCode() { return couponCode; } public String getGift() { return gift; } public String getRemark() { return remark; } }
第二步:实现建造者(内部类方式)
java
代码解读
复制代码
public final class CouponOrder { // ... 其他代码同上 ... // 建造者(静态内部类) public static class Builder { // 必填参数(通过构造函数强制要求) private final String orderId; private final String userId; private final BigDecimal totalAmount; // 可选参数(提供默认值) private String couponCode = "NO_COUPON"; private String gift = ""; private String remark = ""; private LocalDate couponExpireDate; // 强制必填参数 public Builder(String orderId, String userId, BigDecimal totalAmount) { this.orderId = orderId; this.userId = userId; this.totalAmount = totalAmount; } // 可选参数配置方法(返回this实现链式调用) public Builder withCoupon(String code, LocalDate expireDate) { this.couponCode = code; this.couponExpireDate = expireDate; return this; } public Builder addGift(String gift) { this.gift = gift; return this; } public Builder addRemark(String remark) { this.remark = remark; return this; } public CouponOrder build() { validate(); return new CouponOrder(this); } // 参数校验逻辑 private void validate() { if (orderId == null || orderId.isEmpty()) { throw new IllegalArgumentException("订单ID不能为空"); } if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("订单金额必须大于0"); } if (couponExpireDate != null && couponExpireDate.isBefore(LocalDate.now())) { throw new IllegalArgumentException("优惠券已过期"); } } } }
第三步:使用示例
java
代码解读
复制代码
public class Main { public static void main(String[] args) { // 基础订单 CouponOrder order1 = new CouponOrder.Builder("20230901001", "user123", new BigDecimal("299.00")) .build(); // 含优惠券和赠品的订单 CouponOrder order2 = new CouponOrder.Builder("20230901002", "user456", new BigDecimal("599.00")) .withCoupon("SUMMER-50", LocalDate.of(2023, 9, 30)) .addGift("无线鼠标") .addRemark("请周末配送") .build(); // 错误示例:金额不合法 try { CouponOrder invalidOrder = new CouponOrder.Builder("20230901003", "user789", new BigDecimal("-100")) .build(); } catch (IllegalArgumentException e) { System.out.println("错误捕获: " + e.getMessage()); } } }
代码设计亮点
| 设计点 | 实现方式 | 解决的问题 |
|---|---|---|
| 强制必填参数 | 通过Builder构造函数强制要求 | 避免必填项缺失 |
| 防御性拷贝 | 使用BigDecimal不可变类 | 防止金额被外部修改 |
| 链式调用 | 每个配置方法返回this | 提升代码可读性 |
| 过期校验 | 在build()时检查优惠券有效期 | 业务规则前置校验 |
| 不可变对象 | final修饰字段 + 无setter方法 | 线程安全 + 数据一致性 |
常见问题解决示例
问题:如何实现"满100减20"的优惠券?
java
代码解读
复制代码
public Builder applyDiscount(BigDecimal threshold, BigDecimal discount) { if (totalAmount.compareTo(threshold) >= 0) { this.totalAmount = this.totalAmount.subtract(discount); // 错误!BigDecimal不可变 } return this; }
正确实现方式:
java
代码解读
复制代码
public Builder applyDiscount(BigDecimal threshold, BigDecimal discount) { if (totalAmount.compareTo(threshold) >= 0) { // 创建新的Builder实例(需要重新设计Builder结构) return new Builder(orderId, userId, totalAmount.subtract(discount)) .withCoupon(couponCode, couponExpireDate) .addGift(gift) .addRemark(remark); } return this; }
关键点:当需要修改不可变字段时,必须创建新的Builder实例
扩展:Lombok简化版
java
代码解读
复制代码
import lombok.Builder; import lombok.NonNull; @Builder public class LombokOrder { @NonNull private final String orderId; @NonNull private final String userId; @NonNull private final BigDecimal totalAmount; @Builder.Default private String couponCode = "NO_COUPON"; @Builder.Default private String gift = ""; private LocalDate couponExpireDate; } // 使用方式 LombokOrder order = LombokOrder.builder() .orderId("20230901004") .userId("user100") .totalAmount(new BigDecimal("199.00")) .couponCode("WELCOME-10") .build();
注意:Lombok版本需自行添加参数校验逻辑
总结
通过这个电商优惠券系统的案例,我们实现了:
- 通过建造者模式优雅处理6个以上参数
- 使用不可变对象保证线程安全
- 在build()时集中校验业务规则
- 通过链式调用提升代码可读性
实际项目建议:当你的对象满足以下条件时使用建造者模式:
- 参数数量 ≥ 4个
- 存在必填/选填参数组合
- 需要创建不可变对象
- 存在复杂的参数校验逻辑
五、高级技巧:Lombok实现建造者模式
java
代码解读
复制代码
@Builder public class Computer { private String cpu; private String gpu; private int ram; } // 使用方式 Computer pc = Computer.builder() .cpu("Ryzen 7") .gpu("RX 7900") .ram(32) .build();
六、项目实战建议
-
何时使用
- 当对象需要多个可变参数时
- 需要创建不同表示的对象时(如不同套餐配置)
-
常见坑点
- 忘记调用build()方法
- 未做参数校验导致非法状态
-
性能优化:重用Builder实例的场景
七、常见问题解答
-
Q:建造者模式和工厂模式有什么区别?
A:工厂关注产品类型,建造者关注构建过程
-
Q:是否每个类都需要Builder?
A:参数小于4个时可能过度设计
-
Q:如何保证线程安全?
A:为每个线程创建独立Builder实例
584

被折叠的 条评论
为什么被折叠?



