设计模式——建造者模式模式(AI回答)

设计模式——建造者模式模式(AI回答)

请详细解释建造者模式(Builder)的思路、优缺点和代码示例

建造者模式(Builder)

核心思路

建造者模式解决的是复杂对象的构造问题:当一个对象有很多参数,其中一些必填、一些选填、一些之间还有约束关系时,直接用构造器或一堆 setter 会让调用方很痛苦。

来看问题是怎么出现的。


没有建造者时的两种困境

困境一:伸缩构造器(Telescoping Constructor)

为了应对不同的参数组合,写一堆重载构造器:

public class Pizza { public Pizza(String size) { ... } public Pizza(String size, boolean cheese) { ... } public Pizza(String size, boolean cheese, boolean pepperoni) { ... } public Pizza(String size, boolean cheese, boolean pepperoni, boolean mushroom) { ... } // 继续加参数?继续加重载... } // 调用方完全看不懂这串 true/false 是什么意思 Pizza p = new Pizza("large", true, false, true);

困境二:JavaBean 风格(大量 setter)

Pizza p = new Pizza(); p.setSize("large"); p.setCheese(true); p.setPepperoni(false); // 问题:对象在一堆 setter 调用完之前处于"半构造"状态 // 无法在构造完成时做参数校验,也无法做成不可变对象

两种方式都有缺陷:前者可读性差,后者对象状态不安全。建造者模式同时解决这两个问题。


Java 代码示例

以构建一个 HTTP 请求对象为例,参数多且可选,是建造者的典型场景。

产品类(不可变对象)

public class HttpRequest { // 必填 private final String method; private final String url; // 选填 private final Map<String, String> headers; private final String body; private final int timeoutMs; private final boolean followRedirects; // 构造器私有,只能通过 Builder 创建 private HttpRequest(Builder builder) { this.method = builder.method; this.url = builder.url; this.headers = Collections.unmodifiableMap(builder.headers); this.body = builder.body; this.timeoutMs = builder.timeoutMs; this.followRedirects = builder.followRedirects; } @Override public String toString() { return String.format( "HttpRequest{\n method=%s\n url=%s\n headers=%s\n" + " body=%s\n timeout=%dms\n followRedirects=%b\n}", method, url, headers, body, timeoutMs, followRedirects); } // ── Builder 作为静态内部类 ────────────────────────────── public static class Builder { // 必填字段 private final String method; private final String url; // 选填字段,带默认值 private Map<String, String> headers = new HashMap<>(); private String body = null; private int timeoutMs = 3000; private boolean followRedirects = true; // 必填参数通过构造器传入,保证不可缺少 public Builder(String method, String url) { if (method == null || url == null) throw new IllegalArgumentException("method 和 url 不能为空"); this.method = method; this.url = url; } // 每个 setter 返回 this,支持链式调用 public Builder header(String key, String value) { this.headers.put(key, value); return this; } public Builder body(String body) { this.body = body; return this; } public Builder timeoutMs(int timeoutMs) { if (timeoutMs <= 0) throw new IllegalArgumentException("timeout 必须大于 0"); this.timeoutMs = timeoutMs; return this; } public Builder followRedirects(boolean follow) { this.followRedirects = follow; return this; } // 最终构建,可在此做整体校验 public HttpRequest build() { if ("POST".equals(method) && body == null) throw new IllegalStateException("POST 请求必须提供 body"); return new HttpRequest(this); } } }

调用方

public class Main { public static void main(String[] args) { // 简单 GET 请求,只填必要参数 HttpRequest get = new HttpRequest.Builder("GET", "https://api.example.com/users") .header("Authorization", "Bearer token123") .timeoutMs(5000) .build(); // 完整 POST 请求 HttpRequest post = new HttpRequest.Builder("POST", "https://api.example.com/users") .header("Content-Type", "application/json") .header("Authorization", "Bearer token123") .body("{\"name\": \"张三\", \"age\": 28}") .timeoutMs(8000) .followRedirects(false) .build(); System.out.println(get); System.out.println(post); // 校验生效:POST 不传 body 会抛异常 try { new HttpRequest.Builder("POST", "https://api.example.com/users").build(); } catch (IllegalStateException e) { System.out.println("捕获异常: " + e.getMessage()); } } }

输出:

HttpRequest{ method=GET url=https://api.example.com/users headers={Authorization=Bearer token123} body=null timeout=5000ms followRedirects=true } HttpRequest{ method=POST url=https://api.example.com/users headers={Content-Type=application/json, Authorization=Bearer token123} body={"name": "张三", "age": 28} timeout=8000ms followRedirects=false } 捕获异常: POST 请求必须提供 body

建造者的三个关键设计决策

必填参数放构造器,选填参数放链式方法

// 必填:放构造器,缺了连 Builder 都建不出来 new Builder("POST", "https://...") // 选填:放链式方法,不调就用默认值 .timeoutMs(5000) .body("...")

这比把所有参数都堆进build()要好,IDE 会直接提示你哪些字段必须填。

校验逻辑放build()而不是产品类

build()是构造的最后一步,此时所有参数都已收集完毕,可以做跨字段的整体校验(比如"POST 必须有 body"这种单字段校验做不到的约束)。产品类的构造器只负责赋值,保持简单。

产品类字段全部final

通过 Builder 构造出来的对象是完整的、不可变的,构造完成后没有任何方式能修改它的状态,天然线程安全,也避免了对象被意外篡改。


优缺点

优点:

  • 链式调用可读性强,每个参数都有名字,一眼看清在配置什么
  • 必填参数通过构造器强制保证,选填参数有合理默认值
  • build()时集中校验,构造失败在最早的时间点抛出
  • 产品对象可以是完全不可变的

缺点:

  • 代码量翻倍——每个需要 Builder 的类都要维护一个镜像结构的内部类
  • 参数极少(2~3个且全部必填)时完全不必要,直接构造器更简洁
  • 如果产品类字段很多,Builder 类本身也会变得很长

适用场景判断

参数超过 4 个、或者有选填参数、或者参数之间有约束关系,就值得引入 Builder。Java 生态里OkHttpClientRetrofitAlertDialog(Android)、ProcessBuilder都是经典的 Builder 应用。Lombok 的@Builder注解可以自动生成这套代码,实际项目里能省不少样板代码。