精讀《設計模式 - Builder 生成器》

<section id="nice" data-tool="mdnice編輯器" data-website="https://www.mdnice.com" style="font-size: 16px; color: black; padding: 0 10px; line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; word-break: break-word; word-wrap: break-word; text-align: left; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;"><h1 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 24px;"><span class="prefix" style="display: none;"></span><span class="content">Builder(生成器)</span><span class="suffix"></span></h1>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">Builder(生成器)屬於建立型模式,針對的是單個複雜對象的建立。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">意圖:將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">舉例子</span><span class="suffix"></span></h2>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是看不懂上面的意圖介紹,沒有關係,設計模式須要在平常工做裏用起來,結合例子能夠加深你的理解,下面我準備了三個例子,讓你體會什麼場景下會用到這種設計模式。</p>
<h3 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">搭樂高積木</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">樂高積木是很典型的隨機拼裝場景,你有不少樂高積木,要搭一個小房子都太複雜了,可能不得不看着說明書一步步操做,這就像建立一個複雜的對象,要傳入很是多的參數,並且順序還不能錯。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是不考慮拼裝樂高過程當中的樂趣,你只是想快速獲得一個標準的房子,怎麼樣才能夠最快最省事?</p>
<h3 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">工廠流水線</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">製做一個罐頭要經歷許多步驟,而其中一些步驟好比製做罐頭是通用的,能夠用這個罐頭裝不少東西,好比紅棗罐頭、黃桃罐頭,那工廠流水線是怎麼作到靈活可拓展的呢?</p>
<h3 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 20px;"><span class="prefix" style="display: none;"></span><span class="content">建立數據庫鏈接池</span><span class="suffix" style="display: none;"></span></h3>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">創建一個數據庫鏈接池,咱們須要傳入數據庫的地址、用戶名與密碼、還有要建立多少大小的鏈接池,緩存的位置等等。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">考慮到數據庫必須正確鏈接後纔有效,建立時必須校驗傳入的數據庫地址與密碼的正確性,甚至存儲方式與數據庫類型還有關係,這是一個簡單的 new 實例化能夠解決的嗎?</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">意圖解釋</span><span class="suffix"></span></h2>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在樂高積木的例子中,咱們爲了獲得一個房子其實不須要關心每個積木應該如何擺放,咱們只要交給組裝工廠(一我的或者一個程序)產出標準房子就好了,這其中參數多是 .setHouseType().build() 設置房屋類型,而不須要 new House(block1, block2, ... block999) 傳遞這些不必的參數。其中組裝工廠就是生成器。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在工廠流水線的例子中,流水線就是生成器,一個流水線能夠不經過不一樣組合生成不一樣做用的工廠,黃桃罐頭的流水線能夠理解爲 new Builder().組裝罐頭().放入黃桃().build(),紅棗罐頭的流水線能夠理解爲 new Builder().組裝罐頭().放入紅棗().build(),咱們能夠複用生成器最基礎的函數 組裝罐頭() 將其用於建立不一樣的產品中,複用了組裝基礎能力。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在建立數據庫例子中,咱們能夠先設置一些必要的參數再建立,好比 new Builder().setUrl().setPassword().setType().build(),這樣在最終執行 build 函數的時候,能夠對參數中存在關聯的進行校驗,而獲得的對象也沒法再被修改,這樣比直接暴露數據庫鏈接池對象,再一個值一個值 Set 多了以下好處:</p>
<ol data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: decimal;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">對象沒法被修改,保護了程序穩定性,減小了維護複雜度。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">能夠對參數關聯進行一次性校驗。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立對象以前不會存在中間態,即建立了對象實例,但缺乏部分參數,這可能致使對象沒法正確 work。</section></li></ol>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">意圖:將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">咱們再理解一次意圖,所謂構建與表示分離,就是指一個對象 Persion 並非簡單的 new Persion() 就能夠實例化出來的,若是能夠,那就是構建與表示一體。所謂構建與表示分離,就是指 Persion 只能描述,而不能經過 new Persion() 實例化,將實例化工做經過 Builder 實現,這樣一樣一個構建過程能夠建立不一樣的 Persion 實例。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在樂高積木的例子中,經過樂高建立的房子並非 new House() 出來,而是將構建與表示分離了,工廠流水線中咱們建立一個黃桃罐頭,不是經過 new 黃桃罐頭(),而是經過流水線不一樣拼裝方式來完成,在數據庫例子中,咱們沒有經過 new DB() 的方式建立數據庫,而是經過 Builder 來建立,這都體現了構建與表示的分離。</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">結構圖</span><span class="suffix"></span></h2>

<ul data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">Director 指導器,用來指導構建過程。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">Builder 生成器接口,用來提供一系列構建對象的方法,以及最終的 build 生成對象函數,這個函數裏能夠作一些參數校驗。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">ConcreteBuilderBuilder 的具體實現。</section></li></ul>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">實際上,Builder 模式抽象層次可高可低,咱們上面三個例子都沒有用到指導器與生成器接口,這是由於在代碼不太複雜的狀況下,可使用簡化模型。</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">代碼例子</span><span class="suffix"></span></h2>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">下面例子使用 javascript 編寫。</p>
<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px;"><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">class</span> Director {
<span/> create(concreteBuilder: ConcreteBuilder) {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 建立了一些零件</span>
<span/> concreteBuilder.buildA();
<span/> concreteBuilder.buildB();
<span/>
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 校驗參數已經生成實例</span>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> concreteBuilder.build();
<span/> }
<span/>}
<span/>
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">class</span> HouseBuilder {
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> buildA() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 建立房屋</span>
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// this.xxx = xxx</span>
<span/> }
<span/>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> buildB() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 刷油漆</span>
<span/> }
<span/>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> build() {
<span/> <span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 最終建立實例</span>
<span/> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> House(<span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">/ ..一堆參數 this.xxx.. /</span>);
<span/> }
<span/>}
<span/>
<span/><span class="hljs-comment" style="color: #a0a1a7; font-style: italic; line-height: 26px;">// 接下來是正式使用</span>
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> director = <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> Director();
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> builder = HouseBuilder();
<span/><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">const</span> house = director.create(builder);
<span/>
</pre>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">上面的例子是完整版本的 Builder 模式,抽象了指導器 Director 與生成器 Builder,只要二者都嚴格按照接口實現,咱們能夠:</p>
<ol data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: decimal;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">替換任意 Director,使建立的過程作任意修改。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">替換任意 Builder,使建立的實現作任意修改。</section></li></ol>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">作了任意的改動,均可以獲得不一樣的房子實現,這就是建立與表示分離的好處,咱們能夠經過一樣的構建過程建立不一樣的表示。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">這個 director.create():</p>
<ul data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在搭樂高積木的例子,表示用樂高搭建房屋的過程。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在工程流水線的例子,表示罐頭的組裝構成。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立數據庫鏈接池的例子,表示數據庫鏈接池的建立過程。</section></li></ul>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">而 Builder 以及其函數 buildA buildB 等方法表示具體制造方法,好比:</p>
<ul data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在搭樂高積木的例子,表示如何蓋房子,如何刷油漆。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在工程流水線的例子,表示如何作一個罐頭,如何添加黃桃。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">在建立數據庫鏈接池的例子,表示如何設置數據庫地址,如何設置用戶名密碼等。</section></li></ul>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">對於數據庫的例子中,咱們不只能夠保證建立對象的便捷性,由於不須要傳入過多參數,也保證了對象的正確校驗,同時生成的實例也是不可變的。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">更重要的是,若是使用完整模式,咱們能夠替換 Director 來修改建立數據庫的方式,替換 Builder 來修改具體方法,好比 .setUserName 這個函數不作具體實現,而是統計性能,build() 函數建立的不是一個數據庫鏈接實例,而是一個測試實例。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">再好比前端同一個方法在 JS 和 Node 環境下運行效果不同,咱們能夠實現 BrowserBuildNodeBuild,實現相同的接口,這樣能夠共享相同的建立過程,建立不一樣環境能夠運行的實例。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">能夠看到,使用 Builder 模式能夠保證建立對象的便捷與穩定性,還留了足夠的拓展空間改變對象的建立過程與建立方法,具備極強的拓展性。</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">弊端</span><span class="suffix"></span></h2>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">任何設計模式都有其適用場景,反過來也說明了在某些場景下不適用。</p>
<ul data-tool="mdnice編輯器" style="margin-top: 8px; margin-bottom: 8px; padding-left: 25px; color: black; list-style-type: disc;">
<li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">實例化對象很是繁瑣,重複定義了許多對象成員變量的 set 方法,並且也不如 new 看的直觀,也就是場景足夠簡單時,不須要任何地方都用 Builder 實例化對象。</section></li><li><section style="margin-top: 5px; margin-bottom: 5px; line-height: 26px; text-align: left; color: rgb(1,1,1); font-weight: 500;">一個對象只有一種表示時,不必作如此地步的抽象。</section></li></ul>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">上面的例子都是相對複雜的,假設咱們的搭房子的例子中,咱們不是用樂高積木搭建,而是用兩塊半成品模板拼起來就獲得一個房子,那就沒有必要使用 Builder 模式,直接 new House() 便可。</p>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">再者,若是咱們只須要生產各類罐頭,而不須要生產汽車,那麼就不必過分抽象 Builder,把建立汽車的方法也囊括進去,最後,若是咱們的對象只有一種表示時,沒有必要抽象 Builder,也就是流水線若是隻生產黃桃罐頭,就不必把各個生產環節變成可拆卸的,由於也沒有從新組合的須要。</p>
<h2 data-tool="mdnice編輯器" style="margin-top: 30px; margin-bottom: 15px; padding: 0px; font-weight: bold; color: black; font-size: 22px;"><span class="prefix" style="display: none;"></span><span class="content">總結</span><span class="suffix"></span></h2>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">Builder 模式對於建立一個複雜對象特別有用,能夠看下圖加深理解:</p>

<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">最後總結一下什麼時候適合用 Builder 模式:只有當建立過程容許被構造對象有不一樣表示,或者對象複雜到對象描述與建立對象過程值得分離時,才使用 Builder 設計模式。</p>
<blockquote class="multiquote-1" data-tool="mdnice編輯器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">討論地址是:精讀《設計模式 - Builder 生成器》· Issue #273 · dt-fe/weekly</p>
</blockquote>
<p data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。</p>
<blockquote class="multiquote-1" data-tool="mdnice編輯器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">關注 前端精讀微信公衆號</p>
</blockquote>

<blockquote class="multiquote-1" data-tool="mdnice編輯器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;">
<p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)</p>
</blockquote>javascript

<p id="nice-suffix-juejin-container" class="nice-suffix-juejin-container" data-tool="mdnice編輯器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black; margin-top: 20px !important;">本文使用 mdnice 排版</p></section>Builder(生成器)

Builder(生成器)屬於建立型模式,針對的是單個複雜對象的建立。前端

意圖:將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。java

舉例子

若是看不懂上面的意圖介紹,沒有關係,設計模式須要在平常工做裏用起來,結合例子能夠加深你的理解,下面我準備了三個例子,讓你體會什麼場景下會用到這種設計模式。git

搭樂高積木

樂高積木是很典型的隨機拼裝場景,你有不少樂高積木,要搭一個小房子都太複雜了,可能不得不看着說明書一步步操做,這就像建立一個複雜的對象,要傳入很是多的參數,並且順序還不能錯。github

若是不考慮拼裝樂高過程當中的樂趣,你只是想快速獲得一個標準的房子,怎麼樣才能夠最快最省事?web

工廠流水線

製做一個罐頭要經歷許多步驟,而其中一些步驟好比製做罐頭是通用的,能夠用這個罐頭裝不少東西,好比紅棗罐頭、黃桃罐頭,那工廠流水線是怎麼作到靈活可拓展的呢?數據庫

建立數據庫鏈接池

創建一個數據庫鏈接池,咱們須要傳入數據庫的地址、用戶名與密碼、還有要建立多少大小的鏈接池,緩存的位置等等。設計模式

考慮到數據庫必須正確鏈接後纔有效,建立時必須校驗傳入的數據庫地址與密碼的正確性,甚至存儲方式與數據庫類型還有關係,這是一個簡單的 new 實例化能夠解決的嗎?緩存

意圖解釋

在樂高積木的例子中,咱們爲了獲得一個房子其實不須要關心每個積木應該如何擺放,咱們只要交給組裝工廠(一我的或者一個程序)產出標準房子就好了,這其中參數多是 .setHouseType().build() 設置房屋類型,而不須要 new House(block1, block2, ... block999) 傳遞這些不必的參數。其中組裝工廠就是生成器微信

在工廠流水線的例子中,流水線就是生成器,一個流水線能夠不經過不一樣組合生成不一樣做用的工廠,黃桃罐頭的流水線能夠理解爲 new Builder().組裝罐頭().放入黃桃().build(),紅棗罐頭的流水線能夠理解爲 new Builder().組裝罐頭().放入紅棗().build(),咱們能夠複用生成器最基礎的函數 組裝罐頭() 將其用於建立不一樣的產品中,複用了組裝基礎能力。

在建立數據庫例子中,咱們能夠先設置一些必要的參數再建立,好比 new Builder().setUrl().setPassword().setType().build(),這樣在最終執行 build 函數的時候,能夠對參數中存在關聯的進行校驗,而獲得的對象也沒法再被修改,這樣比直接暴露數據庫鏈接池對象,再一個值一個值 Set 多了以下好處:

  1. 對象沒法被修改,保護了程序穩定性,減小了維護複雜度。
  2. 能夠對參數關聯進行一次性校驗。
  3. 在建立對象以前不會存在中間態,即建立了對象實例,但缺乏部分參數,這可能致使對象沒法正確 work。

意圖:將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。

咱們再理解一次意圖,所謂構建與表示分離,就是指一個對象 Persion 並非簡單的 new Persion() 就能夠實例化出來的,若是能夠,那就是構建與表示一體。所謂構建與表示分離,就是指 Persion 只能描述,而不能經過 new Persion() 實例化,將實例化工做經過 Builder 實現,這樣一樣一個構建過程能夠建立不一樣的 Persion 實例。

在樂高積木的例子中,經過樂高建立的房子並非 new House() 出來,而是將構建與表示分離了,工廠流水線中咱們建立一個黃桃罐頭,不是經過 new 黃桃罐頭(),而是經過流水線不一樣拼裝方式來完成,在數據庫例子中,咱們沒有經過 new DB() 的方式建立數據庫,而是經過 Builder 來建立,這都體現了構建與表示的分離。

結構圖

  • Director 指導器,用來指導構建過程。
  • Builder 生成器接口,用來提供一系列構建對象的方法,以及最終的 build 生成對象函數,這個函數裏能夠作一些參數校驗。
  • ConcreteBuilderBuilder 的具體實現。

實際上,Builder 模式抽象層次可高可低,咱們上面三個例子都沒有用到指導器與生成器接口,這是由於在代碼不太複雜的狀況下,可使用簡化模型。

代碼例子

下面例子使用 javascript 編寫。

class Director {
 create(concreteBuilder: ConcreteBuilder) {
 // 建立了一些零件
 concreteBuilder.buildA();
 concreteBuilder.buildB();
 // 校驗參數已經生成實例
 return concreteBuilder.build();
 }
}
 class HouseBuilder {
 public buildA() {
 // 建立房屋
 // this.xxx = xxx
 }
 public buildB() {
 // 刷油漆
 }
 public build() {
 // 最終建立實例
 return new House(/* ..一堆參數 this.xxx.. */);
 }
}
 // 接下來是正式使用
const director = new Director();
const builder = HouseBuilder();
const house = director.create(builder);

上面的例子是完整版本的 Builder 模式,抽象了指導器 Director 與生成器 Builder,只要二者都嚴格按照接口實現,咱們能夠:

  1. 替換任意 Director,使建立的過程作任意修改。
  2. 替換任意 Builder,使建立的實現作任意修改。

作了任意的改動,均可以獲得不一樣的房子實現,這就是建立與表示分離的好處,咱們能夠經過一樣的構建過程建立不一樣的表示。

這個 director.create()

  • 在搭樂高積木的例子,表示用樂高搭建房屋的過程。
  • 在工程流水線的例子,表示罐頭的組裝構成。
  • 在建立數據庫鏈接池的例子,表示數據庫鏈接池的建立過程。

Builder 以及其函數 buildA buildB 等方法表示具體制造方法,好比:

  • 在搭樂高積木的例子,表示如何蓋房子,如何刷油漆。
  • 在工程流水線的例子,表示如何作一個罐頭,如何添加黃桃。
  • 在建立數據庫鏈接池的例子,表示如何設置數據庫地址,如何設置用戶名密碼等。

對於數據庫的例子中,咱們不只能夠保證建立對象的便捷性,由於不須要傳入過多參數,也保證了對象的正確校驗,同時生成的實例也是不可變的。

更重要的是,若是使用完整模式,咱們能夠替換 Director 來修改建立數據庫的方式,替換 Builder 來修改具體方法,好比 .setUserName 這個函數不作具體實現,而是統計性能,build() 函數建立的不是一個數據庫鏈接實例,而是一個測試實例。

再好比前端同一個方法在 JS 和 Node 環境下運行效果不同,咱們能夠實現 BrowserBuildNodeBuild,實現相同的接口,這樣能夠共享相同的建立過程,建立不一樣環境能夠運行的實例。

能夠看到,使用 Builder 模式能夠保證建立對象的便捷與穩定性,還留了足夠的拓展空間改變對象的建立過程與建立方法,具備極強的拓展性。

弊端

任何設計模式都有其適用場景,反過來也說明了在某些場景下不適用。

  • 實例化對象很是繁瑣,重複定義了許多對象成員變量的 set 方法,並且也不如 new 看的直觀,也就是場景足夠簡單時,不須要任何地方都用 Builder 實例化對象。
  • 一個對象只有一種表示時,不必作如此地步的抽象。

上面的例子都是相對複雜的,假設咱們的搭房子的例子中,咱們不是用樂高積木搭建,而是用兩塊半成品模板拼起來就獲得一個房子,那就沒有必要使用 Builder 模式,直接 new House() 便可。

再者,若是咱們只須要生產各類罐頭,而不須要生產汽車,那麼就不必過分抽象 Builder,把建立汽車的方法也囊括進去,最後,若是咱們的對象只有一種表示時,沒有必要抽象 Builder,也就是流水線若是隻生產黃桃罐頭,就不必把各個生產環節變成可拆卸的,由於也沒有從新組合的須要。

總結

Builder 模式對於建立一個複雜對象特別有用,能夠看下圖加深理解:

最後總結一下什麼時候適合用 Builder 模式:只有當建立過程容許被構造對象有不一樣表示,或者對象複雜到對象描述與建立對象過程值得分離時,才使用 Builder 設計模式。

討論地址是: 精讀《設計模式 - Builder 生成器》· Issue #273 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證
相關文章
相關標籤/搜索