【 设计模式 | 创建型模式 建造者模式 】

摘要:本文阐述建造者模式,针对复杂对象,拆分 "部件创建" 与 "组装流程",含产品、抽象 / 具体建造者、指挥者四角色。以共享单车为例实现,解耦构建与表示,扩展用于多参数对象,还讲 @Builder 注解及手动实现场景,适相似部件产品。

1 建造者模式

1.1 概述

建造者模式针对 "复杂对象" 设计,核心逻辑是:

  1. 把对象的 "部件创建"(如汽车的发动机、底盘制造)交给 Builder 负责,"部件组装顺序 / 方式"(如先装底盘再装发动机)交给 Director负责;

  2. 用户无需关心对象内部的构造细节只需指定对象类型,即可通过统一流程获得不同表示的复杂对象(如同样的 "造车流程",可造出轿车、SUV)。

1.2 结构

建造者模式通过四个分工明确的角色,实现 "复杂对象构建与表示分离",具体定义如下:

  1. 抽象建造者类(Builder):作为建造者的 "规则定义者",仅通过接口规定"创建复杂对象需包含哪些部件"(如定义 "造电脑需创建 CPU、内存、主板"),不涉及部件的具体创建逻辑,为具体建造者提供统一的实现标准。

  2. 具体建造者类(ConcreteBuilder):作为抽象建造者的 "落地执行者",实现 Builder 接口中定义的部件创建方法(如 "造游戏本 CPU"),负责具体部件的实例化 ;同时在所有部件创建完成后,提供获取最终产品实例的方法 (如getProduct()),直接产出完整对象。

  3. **产品类(Product):**即要创建的 "复杂对象本身",包含多个需组装的部件(如电脑包含 CPU等),其内部结构复杂,需通过 Builder 创建部件、Director 组装,最终形成可用实例。

  4. **指挥者类(Director):**作为 "装配流程管理者",接收具体建造者实例,不关注产品的具体部件细节,仅负责调用 Builder 的部件创建方法,并按预设顺序 / 逻辑组装部件(如 "先装主板→再装 CPU→最后装内存"),确保复杂对象完整、有序地构建。

类图如下:

1.3 实例

创建共享单车

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。

这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。

具体的代码如下:

java 复制代码
//自行车类
public class Bike {
    private String frame;
    private String seat;
​
    public String getFrame() {
        return frame;
    }
​
    public void setFrame(String frame) {
        this.frame = frame;
    }
​
    public String getSeat() {
        return seat;
    }
​
    public void setSeat(String seat) {
        this.seat = seat;
    }
}
​
// 抽象 builder 类
public abstract class Builder {
​
    protected Bike mBike = new Bike();
​
    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
}
​
//摩拜单车Builder类
public class MobikeBuilder extends Builder {
​
    @Override
    public void buildFrame() {
        mBike.setFrame("铝合金车架");
    }
​
    @Override
    public void buildSeat() {
        mBike.setSeat("真皮车座");
    }
​
    @Override
    public Bike createBike() {
        return mBike;
    }
}
​
//ofo单车Builder类
public class OfoBuilder extends Builder {
​
    @Override
    public void buildFrame() {
        mBike.setFrame("碳纤维车架");
    }
​
    @Override
    public void buildSeat() {
        mBike.setSeat("橡胶车座");
    }
​
    @Override
    public Bike createBike() {
        return mBike;
    }
}
​
//指挥者类
public class Director {
    private Builder mBuilder;
​
    public Director(Builder builder) {
        mBuilder = builder;
    }
​
    public Bike construct() {
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}
​
//测试类
public class Client {
    public static void main(String[] args) {
        showBike(new OfoBuilder());
        showBike(new MobikeBuilder());
    }
    private static void showBike(Builder builder) {
        Director director = new Director(builder);
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

1.4 代码详解

这段这段代码是建造者模式的经典实现,用 "组装自行车" 的场景展示了模式的核心逻辑 ------把 "造什么部件" 和 "怎么组装" 分开,让同一套组装流程能造出不同的自行车。我们一步步拆解:

一、先明确四个核心角色(对应代码)

角色 代码中的类 通俗理解
产品(Product) Bike 最终要造的 "自行车",有车架、车座等部件
抽象建造者(Builder) Builder 抽象类 规定 "造自行车必须做哪些事"(造车架、车座、出成品)
具体建造者(ConcreteBuilder) MobikeBuilderOfoBuilder 具体品牌的 "造车师傅",负责造该品牌的车架和车座
指挥者(Director) Director "组装流程管理者",负责按顺序调用 "造车步骤"

二、逐行看懂代码逻辑

1. Bike 类(产品)

java 复制代码
public class Bike {
    private String frame; // 车架
    private String seat;  // 车座
    // get/set方法:用于设置和获取部件
}

就是一辆 "空自行车",只有两个部件(车架、车座),等待被组装。

2. Builder 抽象类(抽象建造者)

java 复制代码
public abstract class Builder {
    protected Bike mBike = new Bike(); // 准备一辆空车
    
    // 抽象方法:规定必须造车架
    public abstract void buildFrame();
    // 抽象方法:规定必须造车座
    public abstract void buildSeat();
    // 抽象方法:规定必须返回造好的车
    public abstract Bike createBike();
}

不具体造车,只定**"规矩"**:不管造什么自行车,都得先造车架、再造车座,最后交出成品。

提前准备了一辆空Bike,让子类(具体建造者)直接用。

3. 具体建造者(MobikeBuilderOfoBuilder

java 复制代码
public class MobikeBuilder extends Builder {
    @Override
    public void buildFrame() {
        mBike.setFrame("铝合金车架"); // 摩拜的车架是铝合金的
    }
    @Override
    public void buildSeat() {
        mBike.setSeat("真皮车座");   // 摩拜的车座是真皮的
    }
    @Override
    public Bike createBike() {
        return mBike; // 把造好的摩拜车交出去
    }
}

是 "具体干活的",实现了抽象类的方法:明确自己品牌的车架、车座用什么材料。

OfoBuilder 同理,只是部件不同(碳纤维车架、橡胶车座)。

4. Director 类(指挥者)

java 复制代码
public class Director {
    private Builder mBuilder; // 接收一个"造车师傅"
    
    public Director(Builder builder) {
        mBuilder = builder;
    }
    
    // 组装流程:先造车架,再造车座,最后出成品
    public Bike construct() {
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}

不关心 "用什么材料造车",只负责 "组装步骤":先装车架,再装车座,保证流程正确。

不管传入的是摩拜还是 Ofo 的 "师傅",都按这个流程组装。

5. 测试类(Client

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 造一辆Ofo:传入Ofo的"师傅"
        showBike(new OfoBuilder());
        // 造一辆摩拜:传入摩拜的"师傅"
        showBike(new MobikeBuilder());
    }
    
    private static void showBike(Builder builder) {
        Director director = new Director(builder); // 让指挥者管理这个师傅
        Bike bike = director.construct(); // 开始组装
        // 打印车的部件
        System.out.println(bike.getFrame()); 
        System.out.println(bike.getSeat());
    }
}

调用逻辑:用户只需传入对应的 Builder,剩下的组装流程由指挥者搞定。

注意:

上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

java 复制代码
// 抽象 builder 类
public abstract class Builder {
​
    protected Bike mBike = new Bike();
​
    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    public Bike construct() {
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

**说明:**这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。

1.4 优缺点

优点:

**1.**建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。

2.在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

**3.**可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

4.建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则

缺点:

造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

1.5 模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

重构前代码如下:

java 复制代码
public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
​
    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }
​
    public String getCpu() {
        return cpu;
    }
​
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
​
    public String getScreen() {
        return screen;
    }
​
    public void setScreen(String screen) {
        this.screen = screen;
    }
​
    public String getMemory() {
        return memory;
    }
​
    public void setMemory(String memory) {
        this.memory = memory;
    }
​
    public String getMainboard() {
        return mainboard;
    }
​
    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }
​
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}
​
public class Client {
    public static void main(String[] args) {
        //构建Phone对象
        Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
        System.out.println(phone);
    }
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。

重构后代码:

java 复制代码
public class Phone {
​
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
​
    private Phone(Builder builder) {
        cpu = builder.cpu;
        screen = builder.screen;
        memory = builder.memory;
        mainboard = builder.mainboard;
    }
​
    public static final class Builder {
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;
​
        public Builder() {}
​
        public Builder cpu(String val) {
            cpu = val;
            return this;
        }
        public Builder screen(String val) {
            screen = val;
            return this;
        }
        public Builder memory(String val) {
            memory = val;
            return this;
        }
        public Builder mainboard(String val) {
            mainboard = val;
            return this;
        }
        public Phone build() {
            return new Phone(this);}
    }
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}
​
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .mainboard("华硕")
                .memory("金士顿")
                .screen("三星")
                .build();
        System.out.println(phone);
    }
}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

1.6 @Builder注解

在大多数情况下,@Builder注解完全可以替代手动编写的建造者模式代码,并且是企业开发中的首选方案。但需要根据具体场景判断,以下是详细分析:

1. 推荐使用@Builder的场景(大多数情况)

  • 普通业务实体类 :如UserOrderPhone等包含多个属性的 POJO 类,使用@Builder可以极大减少模板代码,同时保留建造者模式的所有优势(链式调用、参数清晰、扩展灵活)。
  • 多参数对象创建 :当类有 5 个以上参数(尤其是包含可选参数)时,@Builder生成的new Xxx.Builder().a().b().build()比冗长的构造器或setter调用更优雅。
  • 与其他 Lombok 注解配合 :结合@Data@NoArgsConstructor等注解,可一站式解决gettersetter、构造器、建造者的代码生成,开发效率极高。

示例:只需一行注解,即可获得完整的建造者功能:

java 复制代码
@Data
@Builder
public class Order {
    private Long id;
    private String userId;
    private BigDecimal amount;
    private LocalDateTime createTime;
    // ...更多属性
}

// 使用时
Order order = Order.builder()
        .id(1L)
        .userId("10086")
        .amount(new BigDecimal("99.9"))
        .build();

2. 不适合用@Builder的场景(少数情况)

需要自定义建造逻辑 :若建造过程中需要复杂校验(如 "金额不能为负""时间必须在当前之前")、依赖外部资源(如从配置文件读取默认值),@Builder的自动生成逻辑无法满足,需手动编写Builder并在build()方法中实现自定义逻辑。

java 复制代码
// 手动编写Builder以支持复杂校验
public static class Builder {
    private BigDecimal amount;
    
    public Builder amount(BigDecimal val) {
        this.amount = val;
        return this;
    }
    
    public Order build() {
        // 自定义校验逻辑
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("金额不能为负");
        }
        return new Order(this);
    }
}

项目禁止使用 Lombok:部分团队因 "调试困难""版本兼容问题" 等原因禁用 Lombok,此时必须手动实现建造者模式。

需要暴露Builder接口给外部框架 :若框架需要通过反射调用Builder的特定方法(如某些 ORM 框架的自定义映射),自动生成的Builder可能因方法签名不匹配而失效,需手动编写以保证兼容性。

3. 总结:@Builder是 "最优解",但不是 "万能解"

90% 以上的场景@Builder完全可以替代手动建造者模式,且能大幅提升开发效率,是推荐用法。

特殊场景:当需要自定义建造逻辑(校验、依赖处理等)或项目限制 Lombok 时,才需要手动实现建造者模式。

1.6 不可使用@Builder案例

java 复制代码
import java.math.BigDecimal;
import java.time.LocalDateTime;
​
// 简化版订单实体类(带手动建造者)
public class Order {
    // 订单核心属性
    private Long id;
    private String userId; // 必填
    private BigDecimal amount; // 必填
    private LocalDateTime createTime;
    private String source;
    private boolean isVipOrder; // 金额≥1000自动为true
​
    // 私有构造器,只能通过Builder创建
    private Order(Builder builder) {
        this.id = builder.id;
        this.userId = builder.userId;
        this.amount = builder.amount;
        this.createTime = builder.createTime;
        this.source = builder.source;
        this.isVipOrder = builder.isVipOrder;
    }
​
    // 建造者类
    public static class Builder {
        // 对应订单的属性
        private Long id;
        private String userId;
        private BigDecimal amount;
        private LocalDateTime createTime = LocalDateTime.now(); // 默认当前时间
        private String source = "APP端"; // 默认来源
        
        // 链式设置方法
        public Builder id(Long val) {
            this.id = val;
            return this;
        }
​
        public Builder userId(String val) {
            this.userId = val;
            return this;
        }
​
        public Builder amount(BigDecimal val) {
            this.amount = val;
            // 自动判断是否为VIP订单
            this.isVipOrder = val.compareTo(new BigDecimal("1000")) >= 0;
            return this;
        }
​
        public Builder createTime(LocalDateTime val) {
            this.createTime = val;
            return this;
        }
​
        public Builder source(String val) {
            this.source = val;
            return this;
        }
​
        // 构建订单并进行必要校验
        public Order build() {
            // 简单校验逻辑
            if (userId == null || userId.isEmpty()) {
                throw new IllegalArgumentException("用户ID不能为空");
            }
            if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
                throw new IllegalArgumentException("金额必须大于0");
            }
            if (createTime.isAfter(LocalDateTime.now())) {
                throw new IllegalArgumentException("创建时间不能晚于当前时间");
            }
​
            return new Order(this);
        }
        
        // 用于判断VIP的临时变量
        private boolean isVipOrder;
    }
​
    // Getter方法
    public Long getId() { return id; }
    public String getUserId() { return userId; }
    public BigDecimal getAmount() { return amount; }
    public LocalDateTime getCreateTime() { return createTime; }
    public String getSource() { return source; }
    public boolean isVipOrder() { return isVipOrder; }
​
    @Override
    public String toString() {
        return "Order{id=" + id + ", userId='" + userId + "', amount=" + amount + 
               ", isVip=" + isVipOrder + ", source='" + source + "'}";
    }
}
​
// 私有构造器,只能通过Builder创建
    private Order(Builder builder) {
        this.id = builder.id;
        this.userId = builder.userId;
        this.amount = builder.amount;
        this.createTime = builder.createTime;
        this.source = builder.source;
        this.isVipOrder = builder.isVipOrder;
    }  

这一段私有构造器是建造者模式的 "核心安全保障",作用可以拆解为 3 个关键维度,直接决定了建造者模式的合理性和安全性:

1. 强制 "唯一创建入口":只能通过 Builder 创建 Order 对象

构造器被声明为private,意味着外部代码(如 Client 类)无法直接用new Order(...)创建对象

  • 反例:如果构造器是public,可能有人跳过 Builder,直接写 new Order(null, "user123", new BigDecimal(-100), ...)------ 这样会绕过 Builder 里的校验逻辑(比如金额不能为负、用户 ID 不能为空),导致创建出非法的 Order 对象。

  • 正例:必须通过 new Order.Builder().xxx().build() 创建**,**build()方法里已包含所有校验,确保只有 "合法的参数" 才能进入这个私有构造器,最终生成的 Order 对象一定是符合规则的。

2. 实现 "参数传递":把 Builder 暂存的参数赋值给 Order

Builder 的核心角色是 "参数容器"------ 先通过.userId().amount()等方法,把参数暂存在 Builder 的成员变量里;当调用build()时,会new Order(this)(把 Builder 自身传给 Order 的私有构造器),再通过这行代码:this.userId = builder.userIdthis.amount = builder.amount把 Builder 里暂存的所有参数,一次性赋值给 Order 的属性,完成对象的最终组装。

简单说:Builder 是 "收集参数的篮子",私有构造器是 "把篮子里的参数倒进 Order 的容器"。

3. 保障 "对象不可变":配合无 Setter,避免创建后被篡改

你会发现 Order 类只有Getter没有Setter------ 这是企业开发中 "不可变对象" 的常用设计,而私有构造器是实现这一设计的前提:

  • 因为对象只能通过 Builder 创建(私有构造器限制),且创建后没有Setter方法,所以 Order 对象一旦生成,它的userIdamountisVipOrder等属性就再也不能被修改。

  • 这种设计的好处是线程安全(多线程环境下不用考虑属性被并发修改的问题),且避免了 "对象被意外篡改导致的数据不一致"(如订单创建后,有人偷偷把金额改成负数)。

一句话总结

这行私有构造器的核心作用是:"锁死 Order 的创建入口,确保只有经过 Builder 校验的合法参数才能生成对象,同时完成参数传递和对象不可变的保障" ------ 没有它,建造者模式就失去了 "规范创建流程、保证对象合法性" 的意义。


大功告成!

相关推荐
Chris.Yuan77016 小时前
泛型学习——看透通配符?与PECS 法则
java·学习
这周也會开心17 小时前
云服务器安装JDK、Tomcat、MySQL
java·服务器·tomcat
hrrrrb18 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶18 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
周杰伦_Jay19 小时前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
摇滚侠20 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯1 天前
Spring Boot测试框架详解
java·spring boot·后端
豐儀麟阁贵1 天前
基本数据类型
java·算法
_extraordinary_1 天前
Java SpringMVC(二) --- 响应,综合性练习
java·开发语言
程序员 Harry1 天前
深度解析:使用ZIP流式读取大型PPTX文件的最佳实践
java