設計模式——建造者模式(創建型)

引言

生成器模式是一種創建型設計模式,?使你能夠分步驟創建復雜對象。?該模式允許你使用相同的創建代碼生成不同類型和形式的對象。

?問題

假設有這樣一個復雜對象,?在對其進行構造時需要對諸多成員變量和嵌套對象進行繁復的初始化工作。?這些初始化代碼通常深藏于一個包含眾多參數且讓人基本看不懂的構造函數中;?甚至還有更糟糕的情況,?那就是這些代碼散落在客戶端代碼的多個位置。

?如果為每種可能的對象都創建一個子類,這可能會導致程序變得過于復雜。

例如,?我們來思考如何創建一個?房屋House對象。?建造一棟簡單的房屋,?首先你需要建造四面墻和地板,?安裝房門和一套窗戶,?然后再建造一個屋頂。?但是如果你想要一棟更寬敞更明亮的房屋,?還要有院子和其他設施?(例如暖氣、?排水和供電設備),?那又該怎么辦呢?

最簡單的方法是擴展?房屋基類,?然后創建一系列涵蓋所有參數組合的子類。?但最終你將面對相當數量的子類。?任何新增的參數?(例如門廊類型)?都會讓這個層次結構更加復雜。

另一種方法則無需生成子類。?你可以在?房屋基類中創建一個包括所有可能參數的超級構造函數,?并用它來控制房屋對象。?這種方法確實可以避免生成子類,?但它卻會造成另外一個問題。

?擁有大量輸入參數的構造函數也有缺陷:這些參數也不是每次都要全部用上的。

通常情況下,?絕大部分的參數都沒有使用,?這使得對于構造函數的調用十分不簡潔。?例如,?只有很少的房子有游泳池,?因此與游泳池相關的參數十之八九是毫無用處的。?

解決方案

生成器模式建議將對象構造代碼從產品類中抽取出來,?并將其放在一個名為生成器的獨立對象中。

該模式會將對象構造過程劃分為一組步驟,?比如?build-Walls創建墻壁和?build-Door創建房門創建房門等。?每次創建對象時,?你都需要通過生成器對象執行一系列步驟。?重點在于你無需調用所有步驟,?而只需調用創建特定對象配置所需的那些步驟即可。

當你需要創建不同形式的產品時,?其中的一些構造步驟可能需要不同的實現。?例如,?木屋的房門可能需要使用木頭制造,?而城堡的房門則必須使用石頭制造。

在這種情況下,?你可以創建多個不同的生成器,?用不同方式實現一組相同的創建步驟。?然后你就可以在創建過程中使用這些生成器?(例如按順序調用多個構造步驟)?來生成不同類型的對象。

?例如,?假設第一個建造者使用木頭和玻璃制造房屋,?第二個建造者使用石頭和鋼鐵,?而第三個建造者使用黃金和鉆石。?在調用同一組步驟后,?第一個建造者會給你一棟普通房屋,?第二個會給你一座小城堡,?而第三個則會給你一座宮殿。?但是,?只有在調用構造步驟的客戶端代碼可以通過通用接口與建造者進行交互時,?這樣的調用才能返回需要的房屋。

主管

你可以進一步將用于創建產品的一系列生成器步驟調用抽取成為單獨的主管類。?主管類可定義創建步驟的執行順序,?而生成器則提供這些步驟的實現。

嚴格來說,?你的程序中并不一定需要主管類。?客戶端代碼可直接以特定順序調用創建步驟。?不過,?主管類中非常適合放入各種例行構造流程,?以便在程序中反復使用。

此外,?對于客戶端代碼來說,?主管類完全隱藏了產品構造細節。?客戶端只需要將一個生成器與主管類關聯,?然后使用主管類來構造產品,?就能從生成器處獲得構造結果了。

生成器模式結構

  1. 生成器 (Builder)接口聲明在所有類型生成器中通用的產品構造步驟。
  2. 具體生成器 (ConcreteBuilders)提供構造過程的不同實現。具體生成器也可以構造不遵循通用接口的產品。
  3. 產品 (Products)是最終生成的對象。由不同生成器構造的產品無需屬于同一類層次結構或接口。
  4. 主管 (Director))類定義調用構造步驟的順序,這樣你就可以創建和復用特定的產品配置。
  5. 客戶端(Client)必須將某個生成器對象與主管類關聯。一般情況下,你只需通過主管類構造函數的參數進行一次性關聯即可。此后主管類就能使用生成器對象完成后續所有的構造任務。但在客
  6. 戶端將生成器對象傳遞給主管類制造方法時還有另一種方式。在這種情況下,你在使用主管類生產產品時每次都可以使用不同的生成器。

?偽代碼

下面關于生成器模式的例子演示了你可以如何復用相同的對象構造代碼來生成不同類型的產品——例如汽車?(Car)——及其相應的使用手冊?(Manual)。

汽車是一個復雜對象,?有數百種不同的制造方法。?我們沒有在?汽車類中塞入一個巨型構造函數,?而是將汽車組裝代碼抽取到單獨的汽車生成器類中。?該類中有一組方法可用來配置汽車的各種部件。

如果客戶端代碼需要組裝一輛與眾不同、?精心調教的汽車,?它可以直接調用生成器。?或者,?客戶端可以將組裝工作委托給主管類,?因為主管類知道如何使用生成器制造最受歡迎的幾種型號汽車。

你或許會感到吃驚,?但確實每輛汽車都需要一本使用手冊?(說真的,?誰會去讀它們呢?)。?使用手冊會介紹汽車的每一項功能,?因此不同型號的汽車,?其使用手冊內容也不一樣。?因此,?你可以復用現有流程來制造實際的汽車及其對應的手冊。?當然,?編寫手冊和制造汽車不是一回事,?所以我們需要另外一個生成器對象來專門編寫使用手冊。?該類與其制造汽車的兄弟類都實現了相同的制造方法,?但是其功能不是制造汽車部件,?而是描述每個部件。?將這些生成器傳遞給相同的主管對象,?我們就能夠生成一輛汽車或是一本使用手冊了。

最后一個部分是獲取結果對象。?盡管金屬汽車和紙質手冊存在關聯,?但它們卻是完全不同的東西。?我們無法在主管類和具體產品類不發生耦合的情況下,?在主管類中提供獲取結果對象的方法。?因此,?我們只能通過負責制造過程的生成器來獲取結果對象。

// 只有當產品較為復雜且需要詳細配置時,使用生成器模式才有意義。下面的兩個
// 產品盡管沒有同樣的接口,但卻相互關聯。
class Car is// 一輛汽車可能配備有 GPS 設備、行車電腦和幾個座位。不同型號的汽車(// 運動型轎車、SUV 和敞篷車)可能會安裝或啟用不同的功能。class Manual is// 用戶使用手冊應該根據汽車配置進行編制,并介紹汽車的所有功能。// 生成器接口聲明了創建產品對象不同部件的方法。
interface Builder ismethod reset()method setSeats(……)method setEngine(……)method setTripComputer(……)method setGPS(……)// 具體生成器類將遵循生成器接口并提供生成步驟的具體實現。你的程序中可能會
// 有多個以不同方式實現的生成器變體。
class CarBuilder implements Builder isprivate field car:Car// 一個新的生成器實例必須包含一個在后續組裝過程中使用的空產品對象。constructor CarBuilder() isthis.reset()// reset(重置)方法可清除正在生成的對象。method reset() isthis.car = new Car()// 所有生成步驟都會與同一個產品實例進行交互。method setSeats(……) is// 設置汽車座位的數量。method setEngine(……) is// 安裝指定的引擎。method setTripComputer(……) is// 安裝行車電腦。method setGPS(……) is// 安裝全球定位系統。// 具體生成器需要自行提供獲取結果的方法。這是因為不同類型的生成器可能// 會創建不遵循相同接口的、完全不同的產品。所以也就無法在生成器接口中// 聲明這些方法(至少在靜態類型的編程語言中是這樣的)。//// 通常在生成器實例將結果返回給客戶端后,它們應該做好生成另一個產品的// 準備。因此生成器實例通常會在 `getProduct(獲取產品)`方法主體末尾// 調用重置方法。但是該行為并不是必需的,你也可讓生成器等待客戶端明確// 調用重置方法后再去處理之前的結果。method getProduct():Car isproduct = this.carthis.reset()return product// 生成器與其他創建型模式的不同之處在于:它讓你能創建不遵循相同接口的產品。
class CarManualBuilder implements Builder isprivate field manual:Manualconstructor CarManualBuilder() isthis.reset()method reset() isthis.manual = new Manual()method setSeats(……) is// 添加關于汽車座椅功能的文檔。method setEngine(……) is// 添加關于引擎的介紹。method setTripComputer(……) is// 添加關于行車電腦的介紹。method setGPS(……) is// 添加關于 GPS 的介紹。method getProduct():Manual is// 返回使用手冊并重置生成器。// 主管只負責按照特定順序執行生成步驟。其在根據特定步驟或配置來生成產品時
// 會很有幫助。由于客戶端可以直接控制生成器,所以嚴格意義上來說,主管類并
// 不是必需的。
class Director is// 主管可同由客戶端代碼傳遞給自身的任何生成器實例進行交互。客戶端可通// 過這種方式改變最新組裝完畢的產品的最終類型。主管可使用同樣的生成步// 驟創建多個產品變體。method constructSportsCar(builder: Builder) isbuilder.reset()builder.setSeats(2)builder.setEngine(new SportEngine())builder.setTripComputer(true)builder.setGPS(true)method constructSUV(builder: Builder) is// ……// 客戶端代碼會創建生成器對象并將其傳遞給主管,然后執行構造過程。最終結果
// 將需要從生成器對象中獲取。
class Application ismethod makeCar() isdirector = new Director()CarBuilder builder = new CarBuilder()director.constructSportsCar(builder)Car car = builder.getProduct()CarManualBuilder builder = new CarManualBuilder()director.constructSportsCar(builder)// 最終產品通常需要從生成器對象中獲取,因為主管不知曉具體生成器和// 產品的存在,也不會對其產生依賴。Manual manual = builder.getProduct()

?生成器模式適合應用場景

用生成器模式可避免?“重疊構造函數?(telescoping constructor)”?的出現。

?假設你的構造函數中有十個可選參數,?那么調用該函數會非常不方便;?因此,?你需要重載這個構造函數,?新建幾個只有較少參數的簡化版。?但這些構造函數仍需調用主構造函數,?傳遞一些默認數值來替代省略掉的參數。

class Pizza {Pizza(int size) { …… }Pizza(int size, boolean cheese) { …… }Pizza(int size, boolean cheese, boolean pepperoni) { …… }// ……

生成器模式讓你可以分步驟生成對象,?而且允許你僅使用必須的步驟。?應用該模式后,?你再也不需要將幾十個參數塞進構造函數里了。

當你希望使用代碼創建不同形式的產品?(例如石頭或木頭房屋)?時,?可使用生成器模式。

?如果你需要創建的各種形式的產品,?它們的制造過程相似且僅有細節上的差異,?此時可使用生成器模式。

基本生成器接口中定義了所有可能的制造步驟,?具體生成器將實現這些步驟來制造特定形式的產品。?同時,?主管類將負責管理制造步驟的順序。

?使用生成器構造組合樹或其他復雜對象。

?生成器模式讓你能分步驟構造產品。?你可以延遲執行某些步驟而不會影響最終產品。?你甚至可以遞歸調用這些步驟,?這在創建對象樹時非常方便。

生成器在執行制造步驟時,?不能對外發布未完成的產品。?這可以避免客戶端代碼獲取到不完整結果對象的情況。

實現方法

  1. 清晰地定義通用步驟,?確保它們可以制造所有形式的產品。?否則你將無法進一步實施該模式。

  2. 在基本生成器接口中聲明這些步驟。

  3. 為每個形式的產品創建具體生成器類,?并實現其構造步驟。

    不要忘記實現獲取構造結果對象的方法。?你不能在生成器接口中聲明該方法,?因為不同生成器構造的產品可能沒有公共接口,?因此你就不知道該方法返回的對象類型。?但是,?如果所有產品都位于單一類層次中,?你就可以安全地在基本接口中添加獲取生成對象的方法。

  4. 考慮創建主管類。?它可以使用同一生成器對象來封裝多種構造產品的方式。

  5. 客戶端代碼會同時創建生成器和主管對象。?構造開始前,?客戶端必須將生成器對象傳遞給主管對象。?通常情況下,?客戶端只需調用主管類構造函數一次即可。?主管類使用生成器對象完成后續所有制造任務。?還有另一種方式,?那就是客戶端可以將生成器對象直接傳遞給主管類的制造方法。

  6. 只有在所有產品都遵循相同接口的情況下,?構造結果可以直接通過主管類獲取。?否則,?客戶端應當通過生成器獲取構造結果。

生成器模式優缺點

  • ?你可以分步創建對象,?暫緩創建步驟或遞歸運行創建步驟。
  • ?生成不同形式的產品時,?你可以復用相同的制造代碼。
  • ?單一職責原則。?你可以將復雜構造代碼從產品的業務邏輯中分離出來。
  • ?由于該模式需要新增多個類,?因此代碼整體復雜程度會有所增加。

與其他模式的關系

  • 在許多設計工作的初期都會使用工廠方法模式?(較為簡單,?而且可以更方便地通過子類進行定制),?隨后演化為使用???????抽象工廠模式、?原型模式或生成器模式(更靈活但更加復雜)。

  • 生成器重點關注如何分步生成復雜對象。?抽象工廠專門用于生產一系列相關對象。?抽象工廠會馬上返回產品,?生成器則允許你在獲取產品前執行一些額外構造步驟。

  • 你可以在創建復雜組合模式樹時使用生成器,?因為這可使其構造步驟以遞歸的方式運行。

  • 你可以結合使用生成器和橋接模式:?主管類負責抽象工作,?各種不同的生成器負責實現工作。

  • 抽象工廠、?生成器和原型都可以用???????單例模式來實現。?

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

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

相關文章

Python3開發環境的搭建

1,電腦操作系統的確認 我的是win10、64位的,你們的操作系統可自尋得。 2,Python安裝包的下載 (1)瀏覽器種輸入網址:https://www.python.org 選擇對應的系統(我的是win10/64位) &#xf…

設計模式(二)-創建者模式(5)-建造者模式

一、為何需要建造者模式(Builder)? 在軟件系統中,會存在一個復雜的對象,復雜在于該對象包含了很多不同的功能模塊。該對象里的各個部分都是按照一定的算法組合起來的。 為了要使得復雜對象里的各個部分的獨立性,以及…

騰訊物聯網平臺之規則引擎

1.騰訊物聯網平臺簡介 騰訊云物聯網開發平臺(IoT Explorer)為客戶提供便捷的物聯網開發工具與服務,助力客戶更高效的完成設備接入,并為客戶提供物聯網應用開發及場景服務能力,幫助客戶高效、低成本構建物聯網應用。 ?…

SpringBoot集成系列--RabbitMQ

文章目錄 一、代碼1、添加依賴2、配置RabbitMQ連接3、RabbitMQ配置4、創建生產者5、創建消費者6、測試 二、遇到的問題1、Channel shutdown2、收不到信息3、安裝RabbitMQ&#xff0c;無法訪問控制臺訪問 一、代碼 1、添加依賴 在pom.xml文件中添加RabbitMQ的相關依賴 <de…

uniapp flex:1不生效

包裹view頂層 不能添加 display: flex;<template><view class"container"><tHeader :title"采購管理" :showScrollTar"true" :scroll"scroll" :tabList"tabList" :isFixed"true"change"chang…

<軟考高項備考>《論文專題 - 2 項目選材》

1 AI輔助寫作 AI技術輔助論文寫作包括&#xff1a; 1、百度-文心一言 2、阿里-千義通問 3、科大訊飛-星火認知大模型 4、騰訊-混元大模型 5、ChatGPT 可以輔助論文的選題&#xff0c;架構理論部分的思路&#xff0c;熟悉了解項目中的難點和痛點&#xff0c;拓寬論文的寫作思路…

跟我學c++高級篇——靜態反射實現之二函數接口實現

一、函數反射 在實際的編程中&#xff0c;類和結構體應用最多&#xff0c;但也最難。這里先分析函數反射&#xff0c;類和結構體放到后面在分析。函數是什么&#xff1f;其實在PC看來就是一個地址&#xff0c;在編譯順看來就是一個符號&#xff08;廢話啊&#xff09;。函數反…

Leetcode—228.匯總區間【簡單】

2023每日刷題&#xff08;五十六&#xff09; Leetcode—228.匯總區間 解題思路 我們可以用雙指針left 和 right找出每個區間的左右端點。 遍歷數組&#xff0c;當right 1< n 且 nums[right1]nums[right]1 時&#xff0c;指針right向右移動&#xff0c;否則區間 [left, …

Mysql8和Oracle實際項目中遞歸查詢樹形結構

背景&#xff1a; 項目升級&#xff0c;引入MySQL數據庫&#xff0c;之前一直用的是Oracle數據&#xff0c;在做用戶登錄單位維護的時候&#xff0c;需要返回該用戶所屬單位下的所有子單位。下邊是模擬項目數據實踐的過程。 數據準備&#xff1a; 準備一張單位表&#xff0c…

Flask存儲在內存中的密鑰被讀取

局限性&#xff1a;查找的密鑰具有特征碼 一、Flask環境源碼 1.Flask主文件main.py import os import uuid from flask import Flask, request, session, render_template from cat import catflag "" app Flask(__name__,static_url_path/,static_folderstatic …

51.Go操作kafka示例(kafka-go庫)

文章目錄 一、簡介二、生產者三、消費者 代碼地址&#xff1a;https://gitee.com/lymgoforIT/golang-trick/tree/master/31-kafka-go 一、簡介 之前已經介紹過一個操作kafka的go庫了&#xff0c;28.windows安裝kafka&#xff0c;Go操作kafka示例&#xff08;sarama庫&#xf…

二叉搜索樹的最近公共祖先【數據結構】

二叉搜索樹的最近公共祖先 題目描述 給定一棵二叉搜索樹的先序遍歷序列&#xff0c;要求你找出任意兩結點的最近公共祖先結點&#xff08;簡稱 LCA&#xff09;。 輸入 輸入的第一行給出兩個正整數&#xff1a;待查詢的結點對數 M&#xff08;≤ 1 000&#xff09;和二叉搜索…

基于JavaWeb+SpringBoot+Vue在線拍賣系統的設計和實現

基于JavaWebSpringBootVue在線拍賣系統系統的設計和實現 源碼獲取入口Lun文目錄前言主要技術系統設計功能截圖訂閱經典源碼專欄Java項目精品實戰案例《500套》 源碼獲取 源碼獲取入口 Lun文目錄 摘 要 1 Abstract 1 1 系統概述 4 1.1 概述 4 1.2課題意義 4 1.3 主要內容 4 2 …

Git命令---綁定遠程倉庫

介紹 使用git命令綁定遠程倉庫 命令 git remote add origin https://gitee.com/x.xx.com/test.git

什么是多態

/*** Description 什么是多態*/ package com.oop;import com.oop.demo06.Person; import com.oop.demo06.Student;public class Application {public static void main(String[] args) {//一個對象的實際類型是確定的//new Student();//new Person();//可以指向的引用類型就不確…

C++新經典模板與泛型編程:策略技術中的算法策略

策略技術中的算法策略 在之前博客中funcsum()函數模板中&#xff0c;實現了對數組元素的求和運算。求和在這里可以看作一種算法&#xff0c;擴展一下思路&#xff0c;對數組元素求差、求乘積、求最大值和最小值等&#xff0c;都可以看作算法。而當前的funcsum()函數模板中&…

MySQL使用教程

數據構成了我們日益數字化的社會基礎。想象一下&#xff0c;從移動應用和銀行系統到搜索引擎&#xff0c;再到如 ChatGPT 這樣的先進人工智能聊天機器人&#xff0c;這些工具若沒有數據支撐&#xff0c;將寸步難行。你有沒有好奇過這些海量數據都存放在哪里呢&#xff1f;答案正…

2023年團體程序設計天梯賽——總決賽題

F-L1-1 最好的文檔 有一位軟件工程師說過一句很有道理的話&#xff1a;“Good code is its own best documentation.”&#xff08;好代碼本身就是最好的文檔&#xff09;。本題就請你直接在屏幕上輸出這句話。 輸入格式&#xff1a; 本題沒有輸入。 輸出格式&#xff1a; 在一…

讀excel文件,借助openpyxl工具

讀excel文件&#xff0c;借助openpyxl工具 import osimport requestsos.environ["http_proxy"] "http://127.0.0.1:7890" os.environ["https_proxy"] "http://127.0.0.1:7890"base_url "https://testnet.starscan.io/explore…

ALNS4VRPTWTF

文章概述 文章研究了城市物流背景下帶有第三方轉運設施的車輛路徑問題。與經典的車輛路徑問題不同&#xff0c;這些問題提供了將客戶需求交付給第三方轉運設施&#xff08;如城市集散中心&#xff09;的選擇&#xff0c;并收取一定的費用。為了解決這些挑戰&#xff0c;該研究…