构建者设计模式 Builder

构建者设计模式

构建器设计模式是一种创建性设计模式,可让您逐步构造复杂对象,将构造逻辑与最终表示分开。

它在以下情况下特别有用:

一个对象需要许多可选字段,并且并非每次都需要所有字段。

您希望避免伸缩构造函数或具有多个参数的大型构造函数。

对象构造过程涉及 需要按特定顺序发生的多个步骤。

在构建此类对象时,开发人员通常依赖于具有许多参数的构造函数或为每个字段公开 setter。例如,类 可能具有 、 、 、 和 等字段,导致构造函数重载或对象状态不一致。Usernameemailphoneaddresspreferences

但随着字段数量的增加,这种方法变得难以管理、容易出错,并且违反了单一责任原则------将构造逻辑与业务逻辑混合在一起。

构建器模式通过引入一个单独的构建器类来处理对象创建过程来解决这个问题。客户端使用此构建器逐步构建对象,同时保持最终对象不可变、一致且易于创建。

让我们通过一个真实世界的示例来了解如何应用构建器模式来使复杂的对象创建更干净、更安全、更易于维护。

问题:构建复杂 对象HttpRequest

想象一下,您正在构建一个需要配置和创建 HTTP 请求的系统。每个 字段都可以包含必填字段和可选字段的组合,具体取决于用例。HttpRequest

以下是典型的 HTTP 请求可能包括的内容:

URL(必填)

HTTP 方法(例如 GET、POST、PUT -- 默认为 GET)

标头(可选,多个键值对)

查询参数(可选,多个键值对)

请求正文(可选,通常用于 POST/PUT)

超时(可选,默认为 30 秒)

乍一看,这似乎是可以控制的。但随着可选字段数量的增加,对象构造的复杂性也随之增加。

朴素的方法:伸缩式构造函数

一种常见的方法是使用构造函数重载(通常称为伸缩构造函数反模式),其中定义多个参数数量不断增加的构造函数:

复制代码
import java.util.HashMap;
import java.util.Map;

public class HttpRequestTelescoping {
    private String url;         // Required
    private String method;      // Optional, default GET
    private Map<String, String> headers; // Optional
    private Map<String, String> queryParams; // Optional
    private String body;        // Optional
    private int timeout;        // Optional, default 30s

    public HttpRequestTelescoping(String url) {
        this(url, "GET");
    }
    public HttpRequestTelescoping(String url, String method) {
        this(url, method, null);
    }
    public HttpRequestTelescoping(String url, String method, Map<String, String> headers) {
        this(url, method, headers, null);
    }
    public HttpRequestTelescoping(String url, String method, Map<String, String> headers, Map<String, String> queryParams) {
        this(url, method, headers, queryParams, null);
    }
    public HttpRequestTelescoping(String url, String method, Map<String, String> headers, Map<String, String> queryParams, String body) {
        this(url, method, headers, queryParams, body, 30000);
    }
    public HttpRequestTelescoping(String url, String method, Map<String, String> headers,
                                  Map<String, String> queryParams, String body, int timeout) {
        this.url = url;
        this.method = method;
        this.headers = headers == null ? new HashMap<>() : headers;
        this.queryParams = queryParams == null ? new HashMap<>() : queryParams;
        this.body = body;
        this.timeout = timeout;
        System.out.println("HttpRequest Created: URL=" + url + ", Method=" + method +
                           ", Headers=" + this.headers.size() + ", Params=" + this.queryParams.size() +
                           ", Body=" + (body != null) + ", Timeout=" + timeout);
    }
    // ... getters ...    
}

客户端代码示例

复制代码
public class HttpAppTelescoping {
    public static void main(String[] args) {
        HttpRequestTelescoping req1 = new HttpRequestTelescoping("https://api.example.com/data"); // GET, defaults
        HttpRequestTelescoping req2 = new HttpRequestTelescoping("https://api.example.com/submit", "POST", null, null, "{\"key\":\"value\"}"); // POST with body
        HttpRequestTelescoping req3 = new HttpRequestTelescoping("https://api.example.com/config", "PUT", Map.of("X-API-Key", "secret"), null, "config_data", 5000);
    }    
}

这种方法有什么问题?

虽然它在功能上有效,但随着 对象变得更加复杂,这种设计很快就会变得笨拙且容易出错。

    1. 难以读写
      相同类型的多个参数(例如,,)很容易意外交换参数。StringMap

代码很难一目了然地理解------尤其是当大多数参数是 .null

    1. 容易出错
      客户端必须传递他们不想设置的可选参数,这增加了错误的风险。null

构造函数内部的防御性编程对于避免 s 变得必要。NullPointerException

    1. 不灵活且脆弱
      如果要设置参数 5 而不是 3 和 4,则必须传递 3 和 4。null

您必须遵循确切的参数顺序,这会损害可读性和可用性。

    1. 可扩展性差
      添加新的可选参数需要添加或更改构造函数,这可能会破坏现有代码或强制对客户端进行不必要的更新。

测试和文档变得越来越难以维护。

我们需要什么

我们需要一种更灵活、可读和可维护的方式来构造 对象------尤其是当涉及许多可选值并且需要不同的组合时。HttpRequest

这正是构建器设计模式的用武之地。

构建器模式

Builder 模式将复杂对象的构造与其表示形式分开。

在构建器模式中:

构造逻辑封装在 Builder 中。

最终对象("产品")是通过调用方法创建 的。build()

对象本身通常具有私有或包私有构造函数,强制通过构建器进行构造。

这导致了可读、流畅的代码,易于扩展和修改,而不会破坏现有客户端。

类图

构建器(例如HttpRequestBuilder)

定义配置或设置产品的方法。

通常 从每个方法返回以支持流畅的接口。this

通常作为产品类中的静态嵌套类实现。

ConcreteBuilder(例如StandardHttpRequestBuilder)

实现 接口或直接定义流畅的方法。Builder

维护正在构建的产品的每个部分的状态。

实现返回最终产品实例的方法。build()

产品(例如HttpRequest)

正在构造的最终对象。

可以不可变,只能通过构建器构建。

具有接收构建器内部状态的私有构造函数。

导演(可选)(例如HttpRequestDirector)

使用构建器编排构建过程。

当您想要封装标准配置或可重用的构造序列时很有用。

在现代用法中(尤其是在具有流畅构建器的 Java 中),Director 经常被省略,客户端通过链接方法来承担这个角色。

实现构建器

    1. 创建产品类 (HttpRequest)
      我们首先创建 类------ 我们想要构建的产品。它有多个字段(一些是必需的,一些是可选的),它的构造函数将是私有的,强制客户端通过构建器构造它。HttpRequest

构建器类将被定义为 中的静态嵌套类,构造函数将接受该构建器的实例来初始化字段。HttpRequest

复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;

public class HttpRequest {
    private final String url;         // Required
    private final String method;      // Optional, default GET
    private final Map<String, String> headers; // Optional
    private final Map<String, String> queryParams; // Optional
    private final String body;        // Optional
    private final int timeout;        // Optional, default 30s

    // Private constructor, only accessible by the Builder
    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Collections.unmodifiableMap(new HashMap<>(builder.headers)); // Defensive copy
        this.queryParams = Collections.unmodifiableMap(new HashMap<>(builder.queryParams)); // Defensive copy
        this.body = builder.body;
        this.timeout = builder.timeout;
    }

    // Getters (no setters to ensure immutability)
    public String getUrl() { return url; }
    public String getMethod() { return method; }
    public Map<String, String> getHeaders() { return headers; }
    public Map<String, String> getQueryParams() { return queryParams; }
    public String getBody() { return body; }
    public int getTimeout() { return timeout; }

    @Override
    public String toString() {
        return "HttpRequest{" +
               "url='" + url + '\'' +
               ", method='" + method + '\'' +
               ", headers=" + headers +
               ", queryParams=" + queryParams +
               ", body='" + (body != null ? body.substring(0, Math.min(10, body.length()))+"..." : "null") + '\'' +
               ", timeout=" + timeout +
               '}';
    }

    // --- Static Nested Builder Class ---
    public static class Builder {
        // Required parameter
        private final String url;

        // Optional parameters - initialized to default values
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private Map<String, String> queryParams = new HashMap<>();
        private String body = null;
        private int timeout = 30000; // 30 seconds default

        // Builder constructor for required fields
        public Builder(String url) {
            if (url == null || url.trim().isEmpty()) {
                throw new IllegalArgumentException("URL cannot be null or empty.");
            }
            this.url = url;
        }

        // Setter-like methods for optional fields, returning the Builder for fluency
        public Builder method(String method) {
            this.method = (method == null || method.trim().isEmpty()) ? "GET" : method.toUpperCase();
            return this;
        }

        public Builder header(String key, String value) {
            if (key != null && value != null) {
                this.headers.put(key, value);
            }
            return this;
        }

        public Builder queryParam(String key, String value) {
            if (key != null && value != null) {
                this.queryParams.put(key, value);
            }
            return this;
        }

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

        public Builder timeout(int timeoutMillis) {
            if (timeoutMillis > 0) {
                this.timeout = timeoutMillis;
            }
            return this;
        }

        // The final build method that creates the HttpRequest object
        public HttpRequest build() {
            // Optionally, add validation logic here before creating the object
            // For example, ensure body is present for POST/PUT if required by your design
            if (("POST".equals(method) || "PUT".equals(method)) && (body == null || body.isEmpty())) {
                 System.out.println("Warning: Building " + method + " request without a body for URL: " + url);
            }
            return new HttpRequest(this);
        }
    }    
}
    1. 添加静态嵌套 类Builder
      此构建器类允许客户端通过流畅的界面设置请求的每个部分。

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Collections;

    public class HttpRequest {
    private final String url; // Required
    private final String method; // Optional, default GET
    private final Map<String, String> headers; // Optional
    private final Map<String, String> queryParams; // Optional
    private final String body; // Optional
    private final int timeout; // Optional, default 30s

    复制代码
     // Private constructor, only accessible by the Builder
     private HttpRequest(Builder builder) {
         this.url = builder.url;
         this.method = builder.method;
         this.headers = Collections.unmodifiableMap(new HashMap<>(builder.headers)); // Defensive copy
         this.queryParams = Collections.unmodifiableMap(new HashMap<>(builder.queryParams)); // Defensive copy
         this.body = builder.body;
         this.timeout = builder.timeout;
     }
    
     // Getters (no setters to ensure immutability)
     public String getUrl() { return url; }
     public String getMethod() { return method; }
     public Map<String, String> getHeaders() { return headers; }
     public Map<String, String> getQueryParams() { return queryParams; }
     public String getBody() { return body; }
     public int getTimeout() { return timeout; }
    
     @Override
     public String toString() {
         return "HttpRequest{" +
                "url='" + url + '\'' +
                ", method='" + method + '\'' +
                ", headers=" + headers +
                ", queryParams=" + queryParams +
                ", body='" + (body != null ? body.substring(0, Math.min(10, body.length()))+"..." : "null") + '\'' +
                ", timeout=" + timeout +
                '}';
     }
    
     // --- Static Nested Builder Class ---
     public static class Builder {
         // Required parameter
         private final String url;
    
         // Optional parameters - initialized to default values
         private String method = "GET";
         private Map<String, String> headers = new HashMap<>();
         private Map<String, String> queryParams = new HashMap<>();
         private String body = null;
         private int timeout = 30000; // 30 seconds default
    
         // Builder constructor for required fields
         public Builder(String url) {
             if (url == null || url.trim().isEmpty()) {
                 throw new IllegalArgumentException("URL cannot be null or empty.");
             }
             this.url = url;
         }
    
         // Setter-like methods for optional fields, returning the Builder for fluency
         public Builder method(String method) {
             this.method = (method == null || method.trim().isEmpty()) ? "GET" : method.toUpperCase();
             return this;
         }
    
         public Builder header(String key, String value) {
             if (key != null && value != null) {
                 this.headers.put(key, value);
             }
             return this;
         }
    
         public Builder queryParam(String key, String value) {
             if (key != null && value != null) {
                 this.queryParams.put(key, value);
             }
             return this;
         }
    
         public Builder body(String body) {
             this.body = body;
             return this;
         }
    
         public Builder timeout(int timeoutMillis) {
             if (timeoutMillis > 0) {
                 this.timeout = timeoutMillis;
             }
             return this;
         }
    
         // The final build method that creates the HttpRequest object
         public HttpRequest build() {
             // Optionally, add validation logic here before creating the object
             // For example, ensure body is present for POST/PUT if required by your design
             if (("POST".equals(method) || "PUT".equals(method)) && (body == null || body.isEmpty())) {
                  System.out.println("Warning: Building " + method + " request without a body for URL: " + url);
             }
             return new HttpRequest(this);
         }
     }    

    }

    1. 使用客户端代码中的构建器
      让我们看看使用构建器构建一个是多么容易和可读:HttpRequest

    public class HttpAppBuilder {
    public static void main(String[] args) {
    // Example 1: Simple GET request
    HttpRequest getRequest = new HttpRequest.Builder("https://api.example.com/users")
    .method("GET")
    .header("Accept", "application/json")
    .timeout(5000)
    .build();
    System.out.println("GET Request: " + getRequest);

    复制代码
         // Example 2: POST request with body and custom headers
         HttpRequest postRequest = new HttpRequest.Builder("https://api.example.com/posts")
                                          .method("POST")
                                          .header("Content-Type", "application/json")
                                          .header("X-Auth-Token", "some_secret_token")
                                          .body("{\"title\":\"New Post\",\"content\":\"Hello Builder!\"}")
                                          .queryParam("userId", "123")
                                          .build();
         System.out.println("POST Request: " + postRequest);
    
         // Example 3: Request with only required URL (defaults for others)
         HttpRequest defaultRequest = new HttpRequest.Builder("https://api.example.com/status").build();
         System.out.println("Default Request: " + defaultRequest);
    
         // Example 4: Illustrating potential warning from builder
         HttpRequest putNoBodyRequest = new HttpRequest.Builder("https://api.example.com/resource/1")
                                             .method("PUT")
                                             // .body("updated data") // Body intentionally omitted
                                             .build();
         System.out.println("PUT Request (no body): " + putNoBodyRequest);
    
         // Example of trying to build with invalid required parameter
         try {
             HttpRequest invalidRequest = new HttpRequest.Builder(null).build();
         } catch (IllegalArgumentException e) {
             System.err.println("Error creating request: " + e.getMessage());
         }
     }    

    }

我们取得了什么成就

不需要长构造函数或 参数。null

可选值名称清晰且易于设置。

最终对象是不可变的,并且完全初始化。

可读且流畅的客户端代码。

易于扩展 --- 想要添加新的可选字段?只需向构建器添加一个新方法即可。

复制代码
https://pan.baidu.com/s/1c1oQItiA7nZxz8Rnl3STpw?pwd=yftc
https://pan.quark.cn/s/dec9e4868381
相关推荐
神仙别闹7 分钟前
基于 JSP+Mysql实现MVC房屋租赁系统
java·mysql·mvc
m0_5213290315 分钟前
java-单元测试
java
duration~16 分钟前
SpringAI集成MCP
人工智能·后端·spring·ai
墨雨听阁17 分钟前
8.18网络编程——基于UDP的TFTP文件传输客户端
网络·网络协议·学习·udp
小晶晶京京17 分钟前
day35-负载均衡
运维·网络·网络协议·学习·负载均衡
掉鱼的猫28 分钟前
Java MCP 的鉴权?好简单的啦
java·mcp
Java水解29 分钟前
Java最新面试题(全网最全、最细、附答案)
java·后端·面试
Java水解31 分钟前
java开发面试题(10个常问面试题含答案,亲测有效)
java·后端·面试
CHEN5_0232 分钟前
【Java基础常见辨析】重载与重写,深拷贝与浅拷贝,抽象类与普通类
java·开发语言
用户40993225021232 分钟前
如何用Prometheus和FastAPI打造任务监控的“火眼金睛”?
后端·ai编程·trae