建造者模式:從“參數地獄”到優雅構建

深夜,一條緊急告警刺穿寂靜:核心報表服務因NullPointerException全線崩潰。排查根源,罪魁禍首竟是一個擁有10多個參數的“上帝構造函數”。本文將從這個災難現場出發,引入“鏈式建造者模式”進行重構,并深入Spring AIOkHttp及電商物流、支付網關等真實場景,剖析其Builder是如何優雅地構建復雜契約的。你將徹底掌握這一構建復雜、不可變對象的終極武器,并看透它在現代框架設計中的核心地位。

一場由null引發的生產癱瘓

那是一個發布新功能的夜晚,我們為數據中臺的報表導出功能增加了一個新的篩選條件。看似簡單的改動,上線后卻觸發了大規模的NullPointerException,導致所有異步報表任務失敗。

經過緊急回滾和復盤,問題定位在一個平平無奇的ReportRequest對象的創建上。

image-20250811091027330

“上帝構造函數”的“原罪”

為了創建一個報表請求,開發者需要實例化一個ReportRequest對象,它的構造函數長這樣:

public class ReportRequest {private String reportName;  // 必填private long startDate;     // 必填private long endDate;       // 必填private String filterByUser; // 可選private String filterByDept; // 可選// ... 可能還有10個其他可選參數// “伸縮構造器”反模式:為了應對可選參數,寫了一堆重載構造函數public ReportRequest(String reportName, long startDate, long endDate) {this(reportName, startDate, endDate, null, null, ...);}// ... 還有更多構造函數
}// 調用方的噩夢
ReportRequest request = new ReportRequest("MonthlySalesReport", start, end, null, null, "EU", 0, true, "PDF");

“罪狀”分析:

  1. 可讀性極差:當參數超過5個,尤其是類型相同時,你很難分清哪個null對應哪個參數。這次事故,就是因為一位同事在調用時,將兩個null的位置搞反了。
  2. 維護地獄:每增加一個可選參數,你就得新增一個構造函數,或者修改一長串現有的構造函數鏈。
  3. 無法保證一致性:對象在構造函數執行完畢之前,可能處于一種“半成品”狀態。

鏈式建造者的降維打擊

重構

要解決這個地獄,建造者模式登場了。我們采用在現代開源框架中更流行的鏈式建造者(Fluent Builder)

import com.google.common.base.Preconditions;public class ReportRequest {private final String reportName;  // 必填,設為finalprivate final long startDate;     // 必填,設為finalprivate final long endDate;       // 必填,設為finalprivate final String filterByDept;private final String exportFormat;// ... 其他屬性均為final// 構造函數變為private,只能通過Builder創建private ReportRequest(Builder builder) {this.reportName = builder.reportName;this.startDate = builder.startDate;this.endDate = builder.endDate;this.filterByDept = builder.filterByDept;this.exportFormat = builder.exportFormat;}// 靜態內部類Builderpublic static class Builder {// 必填參數在Builder的構造函數中強制傳入private final String reportName;private final long startDate;private final long endDate;// 可選參數提供默認值private String filterByDept = null;private String exportFormat = "CSV";public Builder(String reportName, long startDate, long endDate) {this.reportName = reportName;this.startDate = startDate;this.endDate = endDate;}// 每一個setter方法都返回Builder自身,實現鏈式調用public Builder byDept(String filterByDept) {this.filterByDept = filterByDept;return this;}public Builder format(String exportFormat) {this.exportFormat = exportFormat;return this;}// build()方法負責創建最終的、不可變的對象public ReportRequest build() {// 可以在這里進行復雜的校驗邏輯Preconditions.checkNotNull(reportName, "Report name cannot be null");Preconditions.checkArgument(startDate < endDate, "Start date must be before end date");return new ReportRequest(this);}}
}// 調用方的春天
ReportRequest request = new ReportRequest.Builder("MonthlySalesReport", start, end).byDept("Sales-EU").format("PDF").build();

降維打擊在哪?

  1. 可讀性.byDept("...") .format("..."),代碼即文檔,清晰明了。
  2. 安全性:必填參數在構造時強制傳入,可選參數通過具名方法設置,徹底告別null的順序混淆。
  3. 不可變性ReportRequest對象的所有字段都是final的,并在build()方法中一次性完成構建。一旦創建,狀態就無法被修改,是線程安全的。

看看大師們的源碼棋譜

建造者模式的威力,遠不止于此。在企業級架構中,它是一種構建復雜“契約”的核心思想。讓我們直接深入源碼和真實業務,看看大師們是如何下這盤棋的。

實戰一:電商統一物流下單

  • 場景:在一個電商平臺,當一個訂單需要發貨時,系統需要調用一個統一的物流服務。這個服務需要整合多家物流公司(順豐、圓通等)的API。創建一個物流下單請求(ShipmentOrder)非常復雜,包含收發件人信息、包裹詳情、保價、代收貨款、簽收回執等大量可選參數。

  • 建造者應用:設計一個ShipmentOrder.Builder,將復雜的下單流程變得清晰可控。

    // 偽代碼
    ShipmentOrder order = new ShipmentOrder.Builder("SF", "order123", sender, recipient).withInsurance(new BigDecimal("5000.00")) // 申請保價.withCod(order.getTotalAmount()) // 代收貨款.requireSignature() // 要求簽收回執.withDeliveryNotes("易碎品,請輕放").build();
    // builder的build()方法內部可以進行組合校驗,
    // 例如“代收貨款金額不能超過保價金額”
    

實戰二:對接銀聯支付網關

  • 場景:對接傳統的金融機構如銀聯(UnionPay)的支付網關時,其API請求報文通常是固定格式(如XML),且包含大量字段,如商戶號、終端號、交易類型、后臺通知地址、風控信息等。

  • 建造者應用:設計一個UnionPayRequest.Builder,不僅負責參數設置,還可以在build()方法中封裝生成最終報文的復雜邏輯

    // 偽代碼
    UnionPayRequest request = new UnionPayRequest.Builder("898310000000001", "order456", amount).withTerminalId("00000001").withNotifyUrl("https://api.my-shop.com/notify/unionpay").withRiskInfo(riskInfoObject) // 傳入復雜的風控對象.build(); // build()方法內部負責將所有參數轉換為XML格式并簽名
    

實戰三:Spring AI與大模型的復雜契約

  • 場景:與AI大模型交互時,請求參數極其復雜且多變。如果用構造函數,那將是史詩級的災難。Spring AIOllamaApi.ChatRequest.Builder為我們展示了完美的應對之道。

    // Spring AI 調用偽代碼
    OllamaChatRequest request = new OllamaChatRequest.Builder("llama3").withMessage(new Message("user", "你好")).withTemperature(0.8f).withFormat("json").build();
    

其設計精髓在于,將一個復雜的AI請求分解為模型、消息、參數等多個可獨立配置的部分,通過鏈式調用清晰地構建出一個完整的、經過校驗的請求契約。

探究OkHttp與Spring的實現

不可變HTTP請求的教科書——OkHttp的Request.Builder

public class Request {final HttpUrl url;final String method;final Headers headers;final RequestBody body;private Request(Builder builder) { /* ... */ }public static class Builder {HttpUrl url;String method;Headers.Builder headers;RequestBody body;public Builder() {this.method = "GET";this.headers = new Headers.Builder();}public Builder url(String url) { /* ... */ return this; }public Builder header(String name, String value) { /* ... */ return this; }public Builder post(RequestBody body) { return method("POST", body); }public Request build() {if (url == null) throw new IllegalStateException("url == null");return new Request(this);}}
}

設計巧思

  • 組合建造者Request.Builder內部還組合了Headers.Builder,將復雜性進一步分解。
  • 默認值與便捷方法:提供了method的默認值GET,以及.post()等便捷方法,提升了API的易用性。
  • 最終校驗:在build()方法中對必填項進行最終校驗。

安全優雅的URL構建——Spring的UriComponentsBuilder

public class UriComponentsBuilder implements UriBuilder, Cloneable {private String scheme;private String host;private final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();public UriComponentsBuilder scheme(String scheme) { this.scheme = scheme; return this; }public UriComponentsBuilder host(String host) { this.host = host; return this; }public UriComponentsBuilder queryParam(String name, Object... values) { /* ... */ return this; }public UriComponents build() {// 在這里執行所有組件的組裝和編碼邏輯return new UriComponents(scheme, ..., queryParams, ...);}
}

設計巧思

  • 關注點分離:將一個URL拆分為scheme, host, queryParams等多個獨立部分。
  • 自動編碼:在build()方法內部負責處理所有參數的URL編碼,將開發者從繁瑣且易錯的工作中解放出來。
  • 可變與不可變分離UriComponentsBuilder自身是可變的,但它最終build()出的UriComponents對象是不可變的。

用構建過程的確定性,對抗對象狀態的不確定性

  1. 告別“上帝構造函數”:當一個類的構造函數參數超過4個,特別是含有多個可選參數時,就應該立刻啟動重構,引入建造者模式。
  2. 鏈式調用是最佳實踐:采用靜態內部類實現的鏈式建造者,是目前最主流、可讀性最強的實現方式。
  3. 建造者賦能不可變性:將目標對象的構造函數設為private,所有字段設為final,僅通過Builderbuild()方法創建實例。這是構建線程安全對象的關鍵一步。
  4. 應對復雜契約的利器:當需要構建的對象的參數列表復雜、易變時(如API請求、AI模型參數、電商訂單),建造者模式是保證代碼可維護性的不二之選。

好的代碼會說話,而建造者模式,就是對象創建時最雄辯的演說家。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93068.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93068.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93068.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

jenkins在windows配置sshpass

我的服務器里jenkins是通過docker安裝的&#xff0c;jenkins與項目都部署在同一臺服務器上還好&#xff0c;但是當需要通過jenkins構建&#xff0c;再通過scp遠程推送到別的服務器上&#xff0c;就出問題了&#xff0c;畢竟不是手動執行scp命令&#xff0c;可以手動輸入密碼&am…

Linux操作系統從入門到實戰(十八)在Linux里面怎么查看進程

Linux操作系統從入門到實戰&#xff08;十八&#xff09;在Linux里面怎么查看進程前言一、如何識別一個進程&#xff1f;—— PID二、怎么查看進程的信息&#xff1f;方式1&#xff1a;通過/proc目錄方式2&#xff1a;用ps命令三、父進程是什么&#xff1f;—— PPID四、bash是…

[TryHackMe](知識學習)---基于堆棧得到緩沖區溢出

1.了解緩沖區溢出WINDOWS程序動態調試工具immunity debuggerhttps://www.immunityinc.com/products/debugger/2.Mona腳本#!/usr/bin/env python3import socket, time, sysip "10.201.99.37"port 1337 timeout 5 prefix "OVERFLOW1 "string prefix &q…

LRU算法與LFU算法

知識點&#xff1a; LRU是Least Recently Used的縮寫&#xff0c;意思是最近最少使用&#xff0c;它是一種Cache替換算法 Cache的容量有限&#xff0c;因此當Cache的容量用完后&#xff0c;而又有新的內容需要添加進來時&#xff0c; 就需要挑選 并舍棄原有的部分內容&#xf…

目標檢測公開數據集全解析:從經典到前沿

目標檢測公開數據集全解析&#xff1a;從經典到前沿 一、引言 目標檢測&#xff08;Object Detection&#xff09;是計算機視覺領域的核心任務之一&#xff0c;旨在在圖像或視頻中識別并定位感興趣的物體。與圖像分類不同&#xff0c;目標檢測不僅需要判斷物體的類別&#xf…

數據備份與進程管理

一、數據備份1.Linux服務器中需要備份的數據&#xff08;1&#xff09;Linux系統重要數據&#xff1a;/root/目錄&#xff0c;/home/目錄&#xff0c;/etc/目錄&#xff08;2&#xff09;安裝服務的數據&#xff1a;Apache&#xff08;配置文件&#xff0c;網頁主目錄&#xff…

docker volume卷入門教程

1. 基礎概念 Docker卷是專門用于持久化容器數據的存儲方案&#xff0c;獨立于容器生命周期。其核心優勢包括&#xff1a; 數據持久化&#xff1a;容器刪除后數據仍保留跨容器共享&#xff1a;多個容器可訪問同一卷備份與遷移&#xff1a;支持直接復制卷數據驅動支持&#xff1a…

計算機網絡——協議

1. 計算機網絡分層1.1 OSI 7層模型應用層表示層會話層傳輸層網絡層數據鏈路層物理層1.2 TCP/IP 4 層模型應用層運輸層網際層網絡接口層1.3 5層體系機構應用層傳輸層網絡層數據鏈路層物理層2. 應用層協議2.1 HTTP協議2.1.1 基本介紹HTTP&#xff08;HyperText Transfer Protocol…

【React】hooks 中的閉包陷阱

在 React Hooks 中的 閉包陷阱&#xff08;Closure Trap&#xff09;在 useEffect、事件回調、定時器等場景里很常見。1. 閉包陷阱是什么 當你在函數組件里定義一個回調&#xff08;比如事件處理函數&#xff09;&#xff0c;這個回調會捕獲當時渲染時的變量值。如果后面狀態更…

校園快遞小程序(騰訊地圖API、二維碼識別、Echarts圖形化分析)

&#x1f388;系統亮點&#xff1a;騰訊地圖API、二維碼識別、Echarts圖形化分析&#xff1b;一.系統開發工具與環境搭建1.系統設計開發工具后端使用Java編程語言的Spring boot框架 項目架構&#xff1a;B/S架構 運行環境&#xff1a;win10/win11、jdk17小程序&#xff1a; 技術…

Python網絡爬蟲(二) - 解析靜態網頁

文章目錄一、網頁解析技術介紹二、Beautiful Soup庫1. Beautiful Soup庫介紹2. Beautiful Soup庫幾種解析器比較3. 安裝Beautiful Soup庫3.1 安裝 Beautiful Soup 43.2 安裝解析器4. Beautiful Soup使用步驟4.1 創建Beautiful Soup對象4.2 獲取標簽4.2.1 通過標簽名獲取4.2.2 通…

【Linux基礎知識系列】第九十四篇 - 如何使用traceroute命令追蹤路由

在網絡環境中&#xff0c;了解數據包從源主機到目標主機的路徑是非常重要的。這不僅可以幫助我們分析網絡連接問題&#xff0c;還可以用于診斷網絡延遲、丟包等問題。traceroute命令是一個強大的工具&#xff0c;它能夠追蹤數據包在網絡中的路徑&#xff0c;顯示每一跳的延遲和…

達夢數據閃回查詢-快速恢復表

Time:2025/08/12Author:skatexg一、環境說明DM數據庫&#xff1a;DM8.0及以上版本二、適用場景研發在誤操作或變更數據后&#xff0c;想馬上恢復表到某個時間點&#xff0c;可以通過閃回查詢功能快速實現&#xff08;通過全量備份恢復時間長&#xff0c;成本高&#xff09;三、…

力扣(LeetCode) ——225 用隊列實現棧(C語言)

題目&#xff1a;用隊列實現棧示例1&#xff1a; 輸入&#xff1a; [“MyStack”, “push”, “push”, “top”, “pop”, “empty”] [[], [1], [2], [], [], []] 輸出&#xff1a; [null, null, null, 2, 2, false] 解釋&#xff1a; MyStack myStack new MyStack(); mySta…

微軟推出AI惡意軟件檢測智能體 Project Ire

開篇 在8月5號&#xff0c;微軟研究院發布了一篇博客文章&#xff0c;在該篇博客中推出了一款名為Project Ire的AI Agent。該Agent可以在無需人類協助的情況下&#xff0c;自主分析和分類二進制文件。它可以在無需了解二進制文件來源或用途的情況下&#xff0c;對文件進行完全的…

哪些對會交由SpringBoot容器管理?

在 Spring Boot 中,交由容器管理的對象通常稱為“Spring Bean”,這些對象的創建、依賴注入、生命周期等由 Spring 容器統一管控。以下是常見的會被 Spring Boot 容器管理的對象類型及識別方式: 一、通過注解聲明的組件(最常見) Spring Boot 通過類級別的注解自動掃描并注…

Android POS應用在android運行常見問題及解決方案

概述 本文檔記錄了在Android POS應用開發過程中遇到的兩個關鍵問題及其解決方案&#xff1a; UnsatisfiedLinkError: couldnt find "libnative.so" 錯誤ActivityNotFoundException 錯誤商戶信息一致性檢查繞過 問題1&#xff1a;UnsatisfiedLinkError - libnative.so…

基于SpringBoot的旅游網站系統

1. 項目簡介 旅游線路管理系統是一個基于Spring Boot的在線旅游服務平臺&#xff0c;提供旅游線路展示、分類、預訂、訂單管理等功能。系統包含前臺用戶界面和后臺管理模塊&#xff0c;支持用戶注冊登錄、線路瀏覽、收藏、下單支付、客服咨詢等核心功能。管理員可管理線路信息、…

CVPR 2025 | 機器人操控 | RoboGround:用“掩碼”中介表示,讓機器人跨場景泛化更聰明

點擊關注gongzhonghao【計算機sci論文精選】1.導讀1.1論文基本信息論文標題&#xff1a;ROBOGROUND: Robotic Manipulation with Grounded Vision-Language Priors作者&#xff1a;Haifeng Huang, Xinyi Chen, Hao Li&#xff0c; Xiaoshen Han, Yilun Chen, Tai Wang, Zehan W…

構建Node.js單可執行應用(SEA)的方法

如果為了降低部署復雜度&#xff0c;可以考慮使用vercel/ncc。除非有特別理由&#xff0c;不建議使用SEA。1. 環境準備1.1. 基礎要求Node.js: > 19.0.0 (推薦最新LTS版本)1.2. 安裝依賴npm install postject typescript1.3. 驗證環境node -v # 確認版本 > 19 ts…