Factory Method(工廠方法)屬於建立型模式,利用工廠方法建立對象實例而不是直接用 New 關鍵字實例化。前端
理解如何寫出工廠方法很簡單,但理解爲何要用工廠方法就須要動動腦子了。工廠方法看似簡單的將 New 替換爲一個函數,實際上是體現了面向接口編程的思路,它建立的對象實際上是一個符合通用接口的通用對象,這個對象的具體實現能夠隨意替換,以達到通用性目的。git
意圖:定義一個用於建立對象的接口,讓子類決定實例化哪個類。Factory Method 使一個類的實例化延遲到其子類。github
若是看不懂上面的意圖介紹,沒有關係,設計模式須要在平常工做裏用起來,結合例子能夠加深你的理解,下面我準備了三個例子,讓你體會什麼場景下會用到這種設計模式。typescript
我本身在家換過燈泡,之前我家裏燈壞掉的時候,我看着這個奇形怪狀的燈管,內心想,這種燈泡和這個燈座應該是一體的,市場上估計很難買到適配我這個燈座的燈泡了。結果等我把燈泡擰下來,跑到門口的五金店去換的時候,店員隨便給了我一個燈泡,我回去隨便擰了一下竟然就能用了。編程
我買這個燈泡的過程就用到了工廠模式,而正是得益於這種模式,讓我能夠方便在家門口就買到能夠用的燈泡。設計模式
卡牌對戰中,卡牌有一些基本屬性,好比攻防、生命值,也符合一些通用約定,好比一回合出擊一塊兒等等,那麼對於戰鬥系統來講,應該怎樣實例化卡牌呢?如何批量操做卡牌,而不是通用功能也要拿到每一個卡牌的實例才能調用?另外每一個卡牌有特殊能力,這些特殊能力又應該如何拓展呢?微信
一個能夠被交互操做的圖形,它能夠用鼠標進行拉伸、旋轉或者移動,不一樣圖形實現這些操做可能並不相同,要存儲的數據也不同,這些數據應該獨立於圖形存儲,咱們的系統若是要對接任意多的圖形,具有強大拓展能力,對象關係應該如何設計呢?架構
在使用工廠方法以前,咱們就要建立一個 用於建立對象的接口,這個接口具有通用性,因此咱們能夠忽略不一樣的實現來作一些通用的事情。函數
換燈泡的例子來講,我去門口五金店買燈泡,而不是拿到燈泡材料本身 New 一個出來,就是由於五金店這個 「工廠」 提供給個人燈泡符合國家接口標準,而我家裏的燈座也符合這個標準,因此燈座不須要知道對接的燈泡是具體哪一個實例,什麼顏色,什麼形狀,這些都無所謂,只要燈泡符合國家標準接口,就能夠對接上。spa
對卡牌對戰的系統來講,全部卡牌都應該實現同一種接口,因此卡牌對戰系統拿到的卡牌應該就是簡單的 Card 類型,這種類型具有基本的卡片操做交互能力,系統就調用這些能力完成基本流程就行了,若是系統直接實例化具體的卡片,那不一樣的卡片類型會致使系統難以維護,卡片間操做也沒法抽象化。
正式這種模式,使得咱們能夠在卡牌的具體實現上作一些特殊功能,好比修改卡片攻擊時效果,修改卡牌銷燬時效果。
對圖形拖拽系統來講,用到了 「鏈接平行的類層次」 這個特性,所謂鏈接平行的類層次,就是指一個圖形,與其對應的操做類是一個平行抽象類,而一個具體的圖形與具體的操做類則是另外一個平行關係,系統只要關注最抽象的 「通用圖形類」 與 「通用操做類」 便可,操做時,底層多是某個具體的 「圓類」 與 「圓操做類」 結合使用,具體的類有不一樣的實現,但都符合同一種接口,所以操做系統才能夠把它們一視同仁,統一操做。
意圖:定義一個用於建立對象的接口,讓子類決定實例化哪個類。Factory Method 使一個類的實例化延遲到其子類。
因此接口是很是重要的,工廠方法第一句話就是 「定義一個用於建立對象的接口」,這個接口就是 Creator
,讓子類,也就是具體的建立類(ConcreteCreator
)決定要實例化哪一個類(ConcreteProduct
)。
所謂使一個類的實例化延遲到其子類,是由於抽象類不知道要實例化哪一個具體類,因此實例化動做只能由具體的子類去作,這樣繞一圈的好處是,咱們能夠將任意多對象看做是同一類事物,作統一的處理,好比 不管何種燈泡實例都知足通用的燈座接口,全部工廠實例化的卡牌都具有玩一局卡牌遊戲的基本功能,任何圖形與交互類都知足特定功能關係,這種思想讓生活和設計獲得了大幅簡化。
Creator
就是工廠方法,ConcreteCreator
是實現了 Creator
的具體工廠方法,每個具體工廠方法生產一個具體的產品 ConcreteProduct
,每一個具體的產品都實現通用產品的特性 Product
。
下面例子使用 typescript 編寫。
// 產品接口 interface Product { save: () => void; } // 工廠接口 interface Creator { createProduct: () => Product; } // 具體產品 class ConcreteProduct implements Product { save = () => {}; } // 具體工廠 class ConcreteCreator implements Creator { createProduct = () => { return new ConcreteProduct(); }; }
建立一個 Product
的子類 ConcreteCreator
,並返回一個實現了 Product
的具體實例 ConcreteProduct
,這樣咱們就能夠方便使用這個工廠了。
工廠方法並非直接調用 new ConcreteCreator().createProduct
那麼簡單,這樣體現不出任何抽象性,真正的場景是,在一個建立產品的流程中,咱們只知道拿到的工廠是 Creator
:
function main(anyCreator: Creator) { const product = anyCreator.createProduct() }
在外面調用 main
函數時,實際傳進去的是一個具體工廠,好比 myCreator
,但關鍵是 main
函數不用關心究竟是哪個具體工廠,只要知道是個工廠就好了,具體對象建立過程交給了其子類。
你也許也發現了,這就是抽象工廠中其中的一步,因此抽象工廠使用了工廠方法。
工廠方法中,每建立一種具體的子類,就要寫一個對應的 ConcreteCreate
,這相對比較笨重,但有意思的是,若是將建立多個對象放到一個 ConcreteCreate
中,就變成了 簡單工廠模式,新增產品要修改已有類不符合開閉模式,反而推薦寫成本文說的這種模式。
彼之毒藥吾之蜜糖,要知道沒有一種設計模式解決全部問題,沒有一種設計模式沒有弊端,而這個弊端不表明這個設計模式很差,一個弊端的出現多是爲了解決另外一個痛點。 要接受不完美的存在,這麼多種設計模式就是對應了不一樣的業務場景,爲合適的場景選擇一種能將優點發揚光大,以致於能掩蓋弊端,就算進行了合理的架構設計。
工廠方法並非簡單把 New 的過程換成了函數,而是抽象出一套面向接口的設計模式:
你看,我要作燈泡,能夠直接作具體的燈泡,也能夠定一個燈泡接口,經過燈泡工廠拿到具體燈泡,燈泡工廠對待全部燈泡的只作流程都是同樣的,不論是中世紀風燈泡,仍是復古燈泡,仍是普通白織燈,都是如出一轍的製做流程,具體怎麼作由具體的子類去實現,這樣咱們能夠統一管理 「燈泡」 這一個通用概念,而忽略不一樣燈泡之間不過重要的差異,程序的可維護性獲得了大幅提高。
討論地址是: 精讀《設計模式 - Factory Method 工廠方法》· Issue #274 · dt-fe/weekly
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證)