精讀《設計模式 - Abstract Factory 抽象工廠》

Abstract Factory(抽象工廠)屬於建立型模式,工廠類模式抽象程度從低到高分爲:簡單工廠模式 -> 工廠模式 -> 抽象工廠模式。javascript

意圖:提供一個接口以建立一系列相關或相互依賴的對象,而無須指定它們具體的類。前端

舉例子

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

汽車工廠

咱們都知道汽車有不少零部件,隨着工業革命帶來的分工,不少零件均可以被輕鬆替換。但實際生活中咱們消費者不肯意這樣,咱們但願買來的寶馬車所包含的零部件都是同一系列的,以保證最大的匹配度,從而帶來更好的性能與溫馨度。git

因此消費者不肯意到輪胎工廠、方向盤工廠、車窗工廠去一個個採購,而是將需求提給了寶馬工廠這家抽象工廠,由這家工廠負責組裝。那你是這家工廠的老闆,已知汽車的組成部件是固定的,只是不一樣配件有不一樣的型號,分別來自不一樣的製造廠商,你須要推出幾款不一樣組合的車型來知足不一樣價位的消費者,你會怎麼設計?github

迷宮遊戲

你作一款迷宮遊戲,已知元素有房間、門、牆,他們之間的組合關係是固定的,你經過一套算法生成隨機迷宮,這套算法調用房間、門、牆的工廠生成對應的實例。但隨着新資料片的放出,你須要生成具備新功能的房間(能夠回覆體力)、新功能的門(須要魔法鑰匙才能打開)、新功能的牆(能夠被炸彈破壞),但修改已有的迷宮生成算法違背了開閉原則(須要在已有對象進行修改),若是你但願生成迷宮的算法徹底不感知新材料的存在,你會怎麼設計?算法

事件聯動

假設咱們作一個前端搭建引擎,如今但願作一套關聯機制,以實現點擊表格組件單元格,能夠彈出一個模態框,內部展現一個折線圖。已知業務方存在定製表格組件、模態框組件、折線圖組件的需求,但組件之間聯動關係是肯定的,你會怎麼設計?canvas

意圖解釋

在汽車工廠的例子中,咱們已知車子的構成部件,爲了組裝成一輛車子,須要以必定方式拼裝部件,而具體用什麼部件是須要可拓展的設計模式

在迷宮遊戲的例子中,咱們已知迷宮的組成部分是房間、門、牆,爲了生成一個迷宮,須要以某種算法生成許多房間、門、牆的實例,而具體用哪一種房間、哪一種門、哪一種牆是這個算法不關心的,是須要可被拓展的微信

在事件聯動的例子中,咱們已知這個表格彈出趨勢圖的交互場景基本組成元素是表格組件、模態框組件、折線圖組件,須要以某種聯動機制讓這三者間產生聯動關係,而具體是什麼表格、什麼模態框組件、什麼折線圖組件是這個事件聯動所不關心的,是須要能夠被拓展的,表格能夠被替換爲任意業務方註冊的表格,只要知足點擊 onClick 機制就能夠。svg

意圖:提供一個接口以建立一系列相關或相互依賴的對象,而無須指定它們具體的類。

這三個例子不正是符合上面的意圖嗎?咱們要設計的抽象工廠就是要 建立一系列相關或相互依賴的對象,在上面的例子中分別是汽車的組成配件、迷宮遊戲的素材、事件聯動的組件。而無須指定它們具體的類,也就說明了咱們不關心車子方向盤用的是什麼牌子,迷宮的房間是否是普通房間,聯動機制的折線圖是否是用 Echarts 畫的,咱們只要描述好他們之間的關係便可,這帶來的好處是,將來咱們拓展新的方向盤、新的房間、新的折線圖時,不須要修改抽象工廠。

結構圖

AbstractFactory 就是咱們要的抽象工廠,描述了建立產品的抽象關係,好比描述迷宮如何生成,表格和趨勢圖怎麼聯動。

至於具體用什麼方向盤、用什麼房間,是由 ConcreteFactory 實現的,因此咱們可能有多個 ConcreteFactory,好比 ConcreteFactory1 實例化的牆壁是普通牆壁,ConcreteFactory2 實例化的牆壁是魔法牆壁,但其對 AbstractFactory 的接口是一致的,因此 AbstractFactory 不須要關心具體調用的是哪個工廠。

AbstractProduct 是產品抽象類,描述了好比方向盤、牆壁、折線圖的建立方法,而 ConcreteProduct 是具體實現產品的方法,好比 ConcreteProduct1 建立的表格是用 canvas 畫的,折線圖是用 G2 畫的,而 ConcreteProduct2 建立的表格是用 div 畫的,折線圖是用 Echarts 畫的。

這樣,當咱們要拓展一個用 Rcharts 畫的折線圖,用 svg 畫的表格,用 div 畫的模態框組成的事件機制時,只須要再建立一個 ConcreteFactory3 作相應的實現便可,再將這個 ConcreteFactory3 傳遞給 AbstractFactory,並不須要修改 AbstractFactory 方法自己。

代碼例子

下面例子使用 javascript 編寫。

class AbstractFactory {
  createProducts(concreteFactory: ConcreteFactory) {
    const productA = concreteFactory.createProductA();
    const productB = concreteFactory.createProductB();
    // 創建 A 與 B 固定的關聯,即使 A 與 B 實現換成任意實現都不受影響
    productA.bind(productB);
  }
}

productA.bind(productB) 是一種抽象表示:

  • 對於汽車工廠的例子,表示組裝汽車的過程。
  • 對於迷宮遊戲的例子,表示生成迷宮的過程。
  • 對於事件聯動的例子,表示建立組件間關聯的過程。

假設咱們的迷宮有兩套素材,分別是普通素材與魔法素材,只要在分別建立普通素材工廠 ConcreteFactoryA,與魔法素材工廠 ConcreteFactoryB,調用 createProducts 時傳入的是普通素材,則產出的就是普通素材搭建的迷宮,傳入的是魔法素材,則產出的就是用魔法素材搭建的迷宮。

當咱們要建立一套新迷宮材料,好比熔岩迷宮,咱們只要建立一套熔岩素材(熔岩房間、熔岩門、熔岩牆壁),再組裝一個 ConcreteFactoryC 熔岩素材生成工廠傳遞給 AbstractFactory.createProducts 便可。

咱們能夠發現,使用抽象工廠模式,咱們能夠輕鬆拓展新的素材,好比拓展一套新的汽車配件,拓展一套新的迷宮素材,拓展一套新的事件聯動組件,這個過程只須要新建類便可,不須要修改任何類,符合開閉原則

弊端

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

仍是上面的例子,若是咱們的需求不是拓展一個新輪子、新牆壁、新折線圖,而是:

  • 汽車工廠要給汽車加一個新部件:自動駕駛系統。
  • 迷宮遊戲要新增一個功能素材:陷阱。
  • 事件聯動要新增一個聯動對象:明細趨勢統計表格。

你看,這種狀況不是爲已有元素新增一套實現,而是實現一些新元素,就會很是複雜,由於咱們不只要爲全部 ConcreteFactory 新增每個元素,還要修改抽象工廠,以將新元素與舊元素間創建聯繫,違背了開閉原則。

所以,對於已有元素固定的系統,適合使用抽象工廠,反之否則。

總結

抽象工廠對新增已有產品的實現適用,對新增一個產品種類不適用,能夠參考結合了例子的下圖加深理解:

拓展一個熔岩素材包是 增長一種產品風格,適合使用抽象工廠設計模式;拓展一個陷阱是 增長一個產品種類,不適合使用抽象工廠設計模式。爲何呢?看下圖:

建立迷宮這個抽象工廠作的事情,是把已有的房間、門、牆壁創建關聯,由於操做的是抽象類,因此拓展一套具體實現(熔岩素材包)對這個抽象工廠沒有感知,這樣作很容易。

但若是新增一個產品種類 - 陷阱,能夠看到,抽象工廠必須將陷阱與前三者從新創建關聯,這就要修改抽象工廠,不符合開閉原則。同時,若是咱們已有素材包 1 ~素材包 999,就須要同時增長 999 個對應的陷阱實現(普通陷阱、魔法陷阱、熔岩陷阱),其工做量會很是大。

所以,只有產品種類穩定時,須要頻繁拓展產品風格時才適合用抽象工廠設計模式。

討論地址是: 精讀《設計模式 - Abstract Factory 抽象工廠》· Issue #271 · dt-fe/weekly

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

關注 前端精讀微信公衆號

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