进 Gang of Four 设计模式:建造者模式

走进 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。


📑 目录

  1. 模式概述
  2. [经典实现:KFC 套餐示例](#经典实现:KFC 套餐示例)
  3. [Demo 分析:角色在代码中如何体现](#Demo 分析:角色在代码中如何体现)
  4. [现代变体:链式建造者(Fluent Builder)](#现代变体:链式建造者(Fluent Builder))
  5. [建造者 vs 工厂模式](#建造者 vs 工厂模式)
  6. 实战扩展:主流框架中的应用
  7. 总结

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 识别规律

当你在框架源码中看到一个类满足以下特征,它几乎一定使用了建造者模式:

  1. 类名BuilderConfigurerWrapperBootstrap 结尾
  2. 方法返回值 总是 return this(为了链式调用)
  3. 产品对象 没有公开 Setter 方法,所有属性通过 build() 一次性注入

7. 总结

核心要点

  • 建造者模式将"构建过程"与"最终表示"分离,让同样的构建步骤可以产出不同的产品变体
  • 与工厂模式的本质区别:工厂关心"造什么产品",建造者关心"怎么组装零件"
  • 两种形态:经典 GoF 风格(有 Director)适用于构建步骤固定的场景;链式变体(静态内部类 + Fluent API)适用于参数灵活、不可变对象的场景

选型建议

场景 推荐实现
参数超过 4-5 个且部分可选 链式建造者(或 Lombok @Builder
需要不可变对象(无 Setter) 链式建造者,build() 中统一校验
产品有多种固定变体且组装步骤一致 经典 GoF 风格(带 Director)
框架 API 设计(如 Spring Security) 双层建造者(主建造者 + 子建造者 + .and()

一句话

当你下次看到参数爆炸的构造函数时,停下来想一想------也许你需要的不只是重载几个构造方法,而是引入一个建造者。


相关推荐
shuoshuohaohao1 小时前
《JavaWeb --2》
java
浩瀚地学1 小时前
【面试算法笔记】0105-数组-螺旋矩阵
java·开发语言·笔记·算法·面试
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第169题】【07_Redis篇】第5题:如何保证缓存一致性?
java·redis·延迟双删·缓存一致性·双写一致性
SimonKing1 小时前
Spring I18N 底层源码大揭秘:你的 MessageSource 到底是怎么找到配置的?
java·后端·程序员
极客先躯1 小时前
高级java每日一道面试题-2026年04月04日-实战篇[Docker]-如何排查容器存储挂载失败的问题?
java·运维·docker·容器·高级面试
Hesionberger1 小时前
动态规划与二分法破解最长递增子序列
java·数据结构·python·算法·leetcode
CodeStats2 小时前
【Linux IO】从文件描述符到硬件中断:Linux IO 系统底层完全拆解
java·linux·开发语言·io·socket
大阿明2 小时前
C++智能指针与RAII机制精讲:彻底根治内存泄漏与野指针
java·jvm·c++
一生了无挂2 小时前
C++面向对象核心精讲:类、对象、封装、权限与生命周期全解析
java·jvm·c++
aaPIXa6222 小时前
C++ 用 Claude Code 的 defending-code-reference-harness:C/C++ 内存漏洞自动发现与修复 Pipeline
java·c语言·c++