走进 Gang of Four 设计模式:建造者模式
说明:不仅告诉你"怎么用",更告诉你"为什么这样设计"
前置知识:Java 面向对象基础(接口、抽象类、多态)、UML 类图基础
📊 设计模式分类总览
GoF 23 种设计模式可按两个维度交叉分类:类/对象 (处理方式) × 创建型/结构型/行为型(目的)。
| 维度 | 创建型(Creational) | 结构型(Structural) | 行为型(Behavioral) |
|---|---|---|---|
| 类(Class) (通过继承复用) | Factory Method 工厂方法 | Adapter(类适配器) | Interpreter(解释器) Template Method(模板方法) |
| 对象(Object) (通过组合/聚合复用) | Abstract Factory(抽象工厂) Builder(建造者) Prototype(原型) Singleton(单例) | Adapter(对象适配器) Bridge(桥接) Composite(组合) Decorator(装饰) Facade(外观) Flyweight(享元) Proxy(代理) | Chain of Resp.(责任链) Command(命令) Iterator(迭代器) Mediator(中介者) Memento(备忘录) Observer(观察者) State(状态) Strategy(策略) Visitor(访问者) |
本文档聚焦 创建型-对象模式 中的 Builder(建造者),其余创建型对象模式包括 Abstract Factory、Prototype、Singleton。
📑 目录
- 模式概述
- [经典实现:KFC 套餐示例](#经典实现:KFC 套餐示例)
- [Demo 分析:角色在代码中如何体现](#Demo 分析:角色在代码中如何体现)
- [现代变体:链式建造者(Fluent Builder)](#现代变体:链式建造者(Fluent Builder))
- [建造者 vs 工厂模式](#建造者 vs 工厂模式)
- 实战扩展:主流框架中的应用
- 总结
1. 模式概述
1.1 要解决什么问题?
核心矛盾:当一个对象的构造需要多个步骤、多个可选参数,或者参数之间存在依赖关系时,直接使用构造函数会导致代码难以阅读和维护。
java
// ❌ 构造函数参数爆炸 ------ 谁记得每个参数的含义?
new Computer("Intel i7", "16GB", "1TB SSD", true, false, true, false);
// 第4个参数是 true 表示什么?显卡?蓝牙?
另一个问题:当对象由多个部件组装而成,且组装顺序有要求时,客户端直接组装容易遗漏步骤或搞错顺序。
解决方案 :将对象的构造过程 与最终表示 分离。让一个专门的"建造者"负责逐步组装,客户端只需告诉建造者要什么,最后调用 build() 获取完整产品。
1.2 模式定义
建造者模式将一个复杂对象的构建过程与其表示相分离,使得同样的构建过程可以创建不同的表示。
这是 GoF 《设计模式》中定义的创建型模式,与工厂模式的本质区别在于:工厂关注"造什么",建造者关注"怎么造"。
1.3 适合的场景
| 场景 | 说明 |
|---|---|
| 构造函数参数过多 | 超过 4-5 个参数,尤其有大量可选参数时 |
| 参数存在依赖关系 | 如 A 必须在 B 之前设置,或 A 和 B 互斥,在 build() 中统一校验 |
| 需要不可变对象 | 对象创建后属性不可变(无 Setter),用 Builder 在创建时一次性注入 |
| 产品有多种变体 | 同样的构建步骤可产生不同表示(如 VegMeal vs NonVegMeal) |
2. 经典实现:KFC 套餐示例
我们以一个快餐店套餐为例,演示建造者模式的完整实现。套餐由汉堡 和冷饮组成,汉堡用纸盒包装,冷饮用瓶子包装。

2.1 定义食物条目和包装接口
java
// ============ 抽象产品接口 ============
public interface Item {
String name();
Packing packing();
float price();
}
public interface Packing {
String pack();
}
2.2 实现包装接口
java
// ============ 具体包装 ============
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
2.3 创建抽象食物类
java
// ============ 抽象食物(实现 Item,提供默认包装) ============
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper(); // 汉堡用纸盒包装
}
@Override
public abstract float price();
}
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle(); // 冷饮用瓶子包装
}
@Override
public abstract float price();
}
2.4 具体产品(汉堡和饮料的多种实现)
java
// ============ 具体产品 ============
public class VegBurger extends Burger {
@Override
public float price() { return 25.0f; }
@Override
public String name() { return "Veg Burger"; }
}
public class ChickenBurger extends Burger {
@Override
public float price() { return 50.5f; }
@Override
public String name() { return "Chicken Burger"; }
}
public class Coke extends ColdDrink {
@Override
public float price() { return 30.0f; }
@Override
public String name() { return "Coke"; }
}
public class Pepsi extends ColdDrink {
@Override
public float price() { return 35.0f; }
@Override
public String name() { return "Pepsi"; }
}
2.5 产品类:Meal(套餐)
java
// ============ 产品(Product) ============
import java.util.ArrayList;
import java.util.List;
public class Meal {
private List<Item> items = new ArrayList<>();
public void addItem(Item item) {
items.add(item);
}
public float getCost() {
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
public void showItems() {
for (Item item : items) {
System.out.print("Item : " + item.name());
System.out.print(", Packing : " + item.packing().pack());
System.out.println(", Price : " + item.price());
}
}
}
2.6 具体建造者:MealBuilder
java
// ============ 具体建造者(Concrete Builder) ============
public class MealBuilder {
public Meal prepareVegMeal() {
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal prepareNonVegMeal() {
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
2.7 客户端
java
// ============ 客户端(Client) ============
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " + vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " + nonVegMeal.getCost());
}
}
2.8 运行结果
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0
Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5
3. Demo 分析:角色在代码中如何体现
建造者模式涉及以下几方,它们在上面的 KFC 示例中各司其职:
| 角色 | 对应代码 | 职责 |
|---|---|---|
| Product(产品) | Meal 类 |
最终要构建的复杂对象,包含多个部件 |
| Builder(抽象建造者) | 本例中由 MealBuilder 直接充当 |
定义构建产品各部件的接口(本例中简化为 prepareVegMeal() / prepareNonVegMeal()) |
| ConcreteBuilder(具体建造者) | MealBuilder |
实现具体构建步骤,决定用什么部件组装 |
| Director(指导者) | 本例中没有显式 Director,Client 直接调用 Builder | 控制构建顺序和流程(完整版中由 Director 统一调用 Builder 的步骤) |
| Client(客户端) | BuilderPatternDemo |
通过 Builder 获取最终产品,不直接参与组装 |
本例中
MealBuilder身兼两职:它既是"建造者"(定义构建方法),又充当了"指导者"(在prepareVegMeal()中规定了先加汉堡、后加饮料的顺序)。在更严格的实现中,Builder 只提供零件组装方法,Director 负责编排顺序。
4. 现代变体:链式建造者(Fluent Builder)
在实际 Java 开发中(尤其配合 Lombok 的 @Builder 注解),我们很少写专门的 Director 类。更常见的做法是将 Builder 作为产品的内部静态类,通过链式调用(Fluent API)创建对象。
4.1 现代版代码示例
java
// ============ 产品类 ============
public class Computer {
// 必选参数
private String cpu;
private String ram;
// 可选参数
private String storage;
private boolean hasGraphicsCard;
// 私有构造函数,只能通过 Builder 创建
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.hasGraphicsCard = builder.hasGraphicsCard;
}
// ============ 静态内部建造者类 ============
public static class Builder {
private String cpu;
private String ram;
private String storage = "256GB SSD"; // 默认值
private boolean hasGraphicsCard = false;
// 必选参数通过建造者构造方法传入
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
// 可选参数返回 Builder 自身,实现链式调用
public Builder storage(String storage) {
this.storage = storage;
return this;
}
public Builder setGraphicsCard(boolean hasGraphicsCard) {
this.hasGraphicsCard = hasGraphicsCard;
return this;
}
// 最终构建产品的方法
public Computer build() {
return new Computer(this);
}
}
}
4.2 客户端调用
java
Computer myComputer = new Computer.Builder("Intel i7", "16GB")
.storage("1TB NVMe")
.setGraphicsCard(true)
.build();
4.3 与经典实现的区别
| 对比维度 | 经典 GoF 实现 | 现代链式变体 |
|---|---|---|
| Director | 有独立的 Director 类控制步骤 | 无 Director,由 Client 直接链式调用 |
| Builder 位置 | 独立的 Builder 类 | 产品内部的静态类 |
| 调用方式 | builder.prepareXxx() |
new Builder().setA().setB().build() |
| 适用场景 | 构建步骤固定、产品变体有限 | 参数灵活、变体多样、不可变对象 |
| Lombok 支持 | 不适用 | @Builder 注解一键生成 |
现代变体的核心优势在于:参数可选、链式可读、构建时统一校验 。
build()方法中可以执行跨字段校验------比如"内存大于 32G 时主板必须支持 DDR5"这类逻辑,不会让产品处于非法状态。
5. 建造者 vs 工厂模式
这两个创建型模式经常被混淆,但关注点截然不同:
| 维度 | 工厂模式 | 建造者模式 |
|---|---|---|
| 关注点 | **"谁"**来创建(对象的类别) | **"如何"**创建(对象的组装步骤) |
| 产品复杂度 | 创建相对标准的对象,通常一步返回 | 创建复杂对象,由多个部件拼装 |
| 结果返回 | 立即返回标准化的产品 | 逐步配置,最后 build() 统一返回 |
| 客户端感知 | 客户端给个名字(如 "CIRCLE"),工厂直接给成品 |
客户端像自助餐,逐步选配,最后结账 |
| 典型类比 | 去车行说"我要一辆SUV",工厂直接推给你现车 | 定制跑车,选引擎、选车漆、选内饰,最后组装交付 |
6. 实战扩展:主流框架中的应用
建造者模式在 Java 生态中无处不在,以下是几个最典型的例子。
6.1 Spring Security:HttpSecurity
Spring Security 的配置本质上是一种双层建造者模式:不仅构建整个安全过滤链,各项具体配置(登录表单、CSRF)也各自有小建造者。
HttpSecurity继承自AbstractConfiguredSecurityBuilder- 调用
.formLogin()时返回一个FormLoginConfigurer(子建造者) .and()从子建造者切回主建造者,继续配置其他模块
java
// 简化后的伪代码,揭示内部原理
public class HttpSecurity extends AbstractSecurityBuilder<DefaultSecurityFilterChain> {
private final List<SecurityConfigurer> configurers = new ArrayList<>();
public FormLoginConfigurer formLogin() {
FormLoginConfigurer configurer = new FormLoginConfigurer();
this.configurers.add(configurer);
return configurer;
}
@Override
protected DefaultSecurityFilterChain performBuild() {
for (SecurityConfigurer configurer : configurers) {
configurer.configure(this);
}
return new DefaultSecurityFilterChain(..., this.filters);
}
}
java
// 客户端使用
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and().formLogin();
6.2 MyBatis-Plus:QueryWrapper
QueryWrapper 解决的核心问题:如何把 Java 代码安全、有序地翻译成 SQL 字符串。
- 内部维护
MergeSegments队列,每次调用.eq()、.like()都是在追加 SQL 片段 - 防止 SQL 注入:值被放入
ParamNameValuePairs占位符映射表,而非直接拼接
java
// MyBatis-Plus 内部装配逻辑简示
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {
protected final MergeSegments expression = new MergeSegments();
public QueryWrapper<T> eq(String column, Object val) {
// 将 "column = ?" 零件格式化后放入 expression 队列
this.appendSqlSegments(() -> column + " = " + formatParam(val));
return this;
}
// 最终 MyBatis 调用 getSqlSegment() 把零件拼接成完整 WHERE 子句
}
java
// 客户端使用
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age", 25)
.like("name", "张")
.orderByDesc("create_time");
6.3 Protocol Buffers(Protobuf)
Protobuf 的 Java 类具有强烈的不可变性------一旦生成不可修改,因此必须通过内部 Builder 创建。
- Builder 内部拥有一套与 Message 完全对称的属性字段
- 使用**位掩码(Bit-field)**记录哪些可选字段被赋值
build()时只序列化有值的字段,节省网络带宽
java
// Protobuf 编译生成的代码结构
public final class UserMessage extends GeneratedMessageV3 {
private int id_;
private Object name_;
private UserMessage(Builder builder) {
this.id_ = builder.id_;
this.name_ = builder.name_;
}
public static Builder newBuilder() { return Builder.create(); }
public static final class Builder extends GeneratedMessageV3.Builder<Builder> {
private int id_;
private Object name_ = "";
private int bitField0_; // 位掩码,记录哪些字段被赋值
public Builder setId(int value) {
id_ = value;
bitField0_ |= 0x00000001;
return this;
}
public UserMessage build() {
return new UserMessage(this);
}
}
}
6.4 Netty:ServerBootstrap
Netty 的服务端启动引导类,配置维度涉及线程模型、通道类型、TCP 参数、处理器链等。
- 如果没有 Builder,构造函数的参数数量和复杂度将无法管理
bind(port).sync()是最终的"构建"动作,在此之前都是装配
java
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() { ... });
ChannelFuture f = b.bind(8080).sync(); // 最终的构建动作
6.5 Java HttpClient
Java 11 引入的 HttpClient 完全基于建造者模式,构建出不可变的请求/客户端对象。
java
// 建造一个客户端
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
// 建造一个请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.timeout(Duration.ofMinutes(2))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"data\":\"test\"}"))
.build();
6.6 Guava:CacheBuilder
Google Guava 的本地缓存组件,配置维度极多(容量、过期策略、并发级别、监听器等),是建造者模式的典范。
java
LoadingCache<String, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(new CacheLoader<String, Graph>() { ... });
6.7 识别规律
当你在框架源码中看到一个类满足以下特征,它几乎一定使用了建造者模式:
- 类名 以
Builder、Configurer、Wrapper或Bootstrap结尾 - 方法返回值 总是
return this(为了链式调用) - 产品对象 没有公开 Setter 方法,所有属性通过
build()一次性注入
7. 总结
核心要点
- 建造者模式将"构建过程"与"最终表示"分离,让同样的构建步骤可以产出不同的产品变体
- 与工厂模式的本质区别:工厂关心"造什么产品",建造者关心"怎么组装零件"
- 两种形态:经典 GoF 风格(有 Director)适用于构建步骤固定的场景;链式变体(静态内部类 + Fluent API)适用于参数灵活、不可变对象的场景
选型建议
| 场景 | 推荐实现 |
|---|---|
| 参数超过 4-5 个且部分可选 | 链式建造者(或 Lombok @Builder) |
| 需要不可变对象(无 Setter) | 链式建造者,build() 中统一校验 |
| 产品有多种固定变体且组装步骤一致 | 经典 GoF 风格(带 Director) |
| 框架 API 设计(如 Spring Security) | 双层建造者(主建造者 + 子建造者 + .and()) |
一句话
当你下次看到参数爆炸的构造函数时,停下来想一想------也许你需要的不只是重载几个构造方法,而是引入一个建造者。