建造者模式实战指南:场景案例+实战代码,新手也能快速上手

一、为什么需要建造者模式?——从痛点出发

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代码)

案例:电商平台优惠券系统

场景需求
设计一个支持多种优惠券组合的订单系统,要求:

  1. 必须包含订单ID、用户ID、总金额
  2. 可选添加优惠券、赠品、备注
  3. 金额必须大于0,优惠券需校验有效期
  4. 保证订单对象的不可变性

第一步:定义产品类(不可变订单)

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版本需自行添加参数校验逻辑


总结

通过这个电商优惠券系统的案例,我们实现了:

  1. 通过建造者模式优雅处理6个以上参数
  2. 使用不可变对象保证线程安全
  3. 在build()时集中校验业务规则
  4. 通过链式调用提升代码可读性

实际项目建议:当你的对象满足以下条件时使用建造者模式:

  • 参数数量 ≥ 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();


六、项目实战建议
  1. 何时使用

    • 当对象需要多个可变参数时
    • 需要创建不同表示的对象时(如不同套餐配置)
  2. 常见坑点

    • 忘记调用build()方法
    • 未做参数校验导致非法状态
  3. 性能优化:重用Builder实例的场景


七、常见问题解答
  1. Q:建造者模式和工厂模式有什么区别?

    A:工厂关注产品类型,建造者关注构建过程

  2. Q:是否每个类都需要Builder?

    A:参数小于4个时可能过度设计

  3. Q:如何保证线程安全?

    A:为每个线程创建独立Builder实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值