目 录CONTENT

文章目录

建造者模式:分离构建过程与最终表示

冰点
2025-12-04 / 0 评论 / 0 点赞 / 10 阅读 / 13155 字

大家好,我是冰点,今天我们继续拆解设计模式,这次要聊的是建造者模式。

前阵子帮同事调试一个“订单创建”的bug时,我发现他写的Order类构造方法里堆了12个参数——有必填的“商品ID”“用户ID”,也有可选的“优惠券ID”“备注信息”“收货地址”,每次创建订单都要记参数顺序,漏填一个就报错。更麻烦的是,有时候需要创建“普通订单”,有时候需要创建“预售订单”(多了“付定金时间”“尾款时间”),用一个构造方法根本兼容不了。后来我建议他用建造者模式重构,把订单的“构建步骤”和“最终对象”分开,代码瞬间清爽了很多。

为什么需要建造者模式?先解决“复杂对象创建”的痛点

很多人会觉得“创建对象不就是new一下吗”,但遇到属性多、构建步骤复杂、存在多种变体的对象时,直接new会踩很多坑。建造者模式的核心价值,就是解决这类复杂对象的创建问题,主要针对三个痛点:

  1. 避免“参数爆炸”的构造方法。像前面的订单类,12个参数的构造方法不仅难记,还容易传错顺序(比如把“金额”和“数量”位置搞反),用建造者模式可以分步骤设置属性,不用一次性传所有参数。

  2. 分离“构建过程”和“对象表示”。比如创建“普通订单”需要3步(设商品、设用户、算金额),创建“预售订单”需要5步(多了设定金、设尾款时间),这些步骤可以交给建造者管理,业务代码不用关心“怎么一步步建订单”,只需要拿到最终的订单对象。

  3. 保证对象创建的完整性和一致性。用建造者模式可以强制执行必要的构建步骤(比如必须先设商品ID才能算金额),避免创建出“缺胳膊少腿”的无效对象(比如没有商品ID的订单)。

简单说,当你需要创建的对象“属性多、步骤多、有多种构建方式”时,建造者模式就是最优解之一。

建造者模式怎么实现?核心是“指挥者+建造者+产品”

它的实现思路很清晰:把对象的构建拆成多个步骤,由“建造者”负责执行步骤,“指挥者”负责控制步骤顺序,最终生成完整的“产品”对象。还是用“订单创建”的场景举例,带你看完整实现。

第一步:定义“产品”类(要创建的复杂对象)

先定义Order类,包含各种属性(必填+可选),注意把构造方法私有化,避免外部直接new,只能通过建造者创建。

// 产品:订单类
public class Order {
    // 必填属性
    private String productId; // 商品ID
    private String userId;    // 用户ID
    private double amount;    // 订单金额
    // 可选属性
    private String couponId;  // 优惠券ID
    private String remark;    // 备注
    // 预售订单专属属性
    private long depositTime; // 付定金时间
    private long balanceTime; // 付尾款时间

    // 私有化构造方法,只能通过建造者创建
    private Order(Builder builder) {
        this.productId = builder.productId;
        this.userId = builder.userId;
        this.amount = builder.amount;
        this.couponId = builder.couponId;
        this.remark = builder.remark;
        this.depositTime = builder.depositTime;
        this.balanceTime = builder.balanceTime;
    }

    // getter方法(省略setter,保证对象创建后不可变,避免后续修改)
    public String getProductId() { return productId; }
    // 其他getter省略...

    // 第二步:定义“建造者”(静态内部类,负责分步构建)
    public static class Builder {
        // 必选属性(直接在Builder中初始化,强制设置)
        private String productId;
        private String userId;
        private double amount;
        // 可选属性(默认值初始化)
        private String couponId = "";
        private String remark = "";
        private long depositTime = 0;
        private long balanceTime = 0;

        // Builder的构造方法,强制传入必选属性
        public Builder(String productId, String userId, double amount) {
            this.productId = productId;
            this.userId = userId;
            this.amount = amount;
        }

        // 可选属性的设置方法,返回Builder本身(链式调用)
        public Builder setCouponId(String couponId) {
            this.couponId = couponId;
            return this; // 关键:返回this,支持链式调用
        }

        public Builder setRemark(String remark) {
            this.remark = remark;
            return this;
        }

        // 预售订单专属属性的设置方法
        public Builder setPresaleInfo(long depositTime, long balanceTime) {
            this.depositTime = depositTime;
            this.balanceTime = balanceTime;
            return this;
        }

        // 最终构建产品的方法
        public Order build() {
            // 这里可以加校验逻辑,保证对象完整性(比如商品ID不能为空)
            if (productId == null || productId.isEmpty()) {
                throw new IllegalArgumentException("商品ID不能为空");
            }
            return new Order(this); // 调用Order的私有构造方法
        }
    }
}

第三步:在业务中使用建造者创建对象

业务代码里不用关心订单的构建步骤,直接通过Builder的链式调用设置属性,最后调用build()拿到完整订单,代码非常简洁。

public class OrderService {
    public static void main(String[] args) {
        // 1. 创建普通订单(只设必填+部分可选属性)
        Order normalOrder = new Order.Builder("prod_001", "user_123", 99.9)
                .setCouponId("coupon_456")
                .setRemark("请尽快发货")
                .build(); // 最终构建订单

        // 2. 创建预售订单(多设预售专属属性)
        Order presaleOrder = new Order.Builder("prod_002", "user_456", 199.9)
                .setCouponId("coupon_789")
                .setPresaleInfo(1699999999000L, 1700000000000L) // 定金时间+尾款时间
                .build();

        // 使用订单对象(调用getter方法)
        System.out.println("普通订单商品ID:" + normalOrder.getProductId());
        System.out.println("预售订单尾款时间:" + presaleOrder.getBalanceTime());
    }
}

(可选)复杂场景下加“指挥者”

如果构建步骤特别复杂(比如创建订单需要先算折扣、再减优惠券、最后加运费),可以加一个“指挥者”类,专门控制步骤顺序,让建造者只负责“执行步骤”,进一步解耦。

// 指挥者:控制订单构建的步骤顺序
public class OrderDirector {
    private Order.Builder builder;

    public OrderDirector(Order.Builder builder) {
        this.builder = builder;
    }

    // 指挥构建“带折扣的普通订单”(固定步骤:设商品→算折扣→设优惠券)
    public Order buildDiscountedOrder(double discount) {
        double discountedAmount = 99.9 * discount; // 算折扣后的金额
        return builder
                .setCouponId("coupon_discount") // 自动加折扣优惠券
                .build();
    }
}

// 业务中使用指挥者
Order.Builder builder = new Order.Builder("prod_003", "user_789", 99.9);
OrderDirector director = new OrderDirector(builder);
Order discountedOrder = director.buildDiscountedOrder(0.8); // 直接拿到8折订单

实际用的时候,我总结的3个关键经验

建造者模式不算复杂,但用的时候容易和其他模式混淆,分享几个我踩过的坑和心得:

  1. 区分“建造者模式”和“工厂模式”。工厂模式是“一步创建对象”,适合创建简单或中等复杂度的对象;建造者模式是“分步创建对象”,适合创建属性多、步骤多的复杂对象。比如“创建简单的支付对象”用工厂,“创建复杂的订单对象”用建造者。

  2. 优先用“静态内部类Builder”。像上面的实现一样,把Builder作为产品类的静态内部类,好处是代码紧凑,且能访问产品类的私有构造方法,避免外部篡改。这是Java中最常用的建造者实现方式(比如StringBuilder、OkHttp的Request.Builder都是这种写法)。

  3. 对象创建后尽量“不可变”。产品类最好只提供getter方法,不提供setter方法,让对象创建后就固定下来,避免后续被意外修改(比如创建订单后,金额被随便改了),保证线程安全和数据一致性。

最后总结:什么时候优先用建造者模式?

记住三个判断标准,满足任一即可考虑:

  • 对象有5个以上属性,且包含必填和可选属性;

  • 对象创建需要多个步骤(比如算金额、校验参数、设置专属属性);

  • 需要创建多种变体的对象(比如普通订单、预售订单、团购订单),但构建步骤有重叠。

接下来我们还会继续拆解其他设计模式,如果你对建造者模式有疑问,比如“怎么用建造者创建集合类型的对象”,或者想看看其他场景(比如创建复杂的文档对象)的实现,都可以评论区留言。

0

评论区