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

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

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图)

核心角色交互图

classDiagram class Computer { -cpu: String -gpu: String -ram: int +Computer() +getCpu() String +getGpu() String +getRam() int } class ComputerBuilder { <> +setCpu(String cpu) ComputerBuilder +setGpu(String gpu) ComputerBuilder +setRam(int ram) ComputerBuilder +build() Computer } class StandardComputerBuilder { -computer: Computer +setCpu(String cpu) ComputerBuilder +setGpu(String gpu) ComputerBuilder +setRam(int ram) ComputerBuilder +build() Computer } class ComputerDirector { -builder: ComputerBuilder +constructGamingPC() Computer +constructOfficePC() Computer } Computer <.. StandardComputerBuilder : 构建 ComputerBuilder <|.. StandardComputerBuilder : 实现 ComputerDirector --> ComputerBuilder : 使用

角色详解 + 代码对照

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实例

相关推荐
AI小智1 小时前
MCP:昙花一现还是未来标准?LangChain 创始人激辩实录
后端
bobz9651 小时前
strongswan IKEv1 proposal 使用
后端
Sans_1 小时前
初识Docker-Compose(包含示例)
后端·docker·容器
信阳农夫2 小时前
Django解析跨域问题
后端·python·django
小华同学ai2 小时前
331K star!福利来啦,搞定所有API开发需求,这个开源神器绝了!
前端·后端·github
hyena2 小时前
基于FastExcel/EasyExcel多线程导出百万级数据Excel文件并利用 ResponseBodyEmitter 与caffeine技术实现进度查询
后端
用户81367016107882 小时前
Spring MVC 请求处理流程
后端
世界哪有真情2 小时前
3月12日最新!Cursor无限续杯
前端·后端·cursor
Asthenia04122 小时前
深入剖析 Java 反射 Method.invoke 的底层原理:MethodAccessor->NativeMethodAccessorImpl
后端