Tsai Mujing 已發佈 2019-10-19

JS設計模式 - 工廠模式

在我鐵人賽完賽之後,我想延伸原來的主題,繼續往下寫一些比較實用型的知識,設計模式就是一系列在開發時常用到的思考模式,所以我想藉由寫文章來推動自己繼續學習與分享。今天要分享的是最常見的設計模式之一:工廠模式 (Factory Method Pattern)。

Outline

  • 什麼是設計模式
  • 什麼是工廠模式,何時要用?
  • 動態的工廠類別

什麼是設計模式

這些前人累積下來且可以直接拿來被使用、解決問題的思考模式就稱為設計模式。因此我認為設計模式不像是某一門學問,反倒像是一連串經驗的累積。設計模式概念的成形最早來自於四人幫整理的「設計模式:可復用物件導向軟體的基礎」這本書裡面。

工廠方法 (Factory Method)模擬了現實生活中「工廠」的概念,生活中的工廠同時具有能夠產出許多相同樣貌的產品,以及生產多種類型產品的能力。例如:在同一個罐頭工廠內,我同時可以生產鮪魚罐頭,也可能可以同時生產鳳梨罐頭,這些產品基本上具有相同的屬性,但內容物跟規格卻可能完全不ㄧ樣。

而從程式語言的角度來看,工廠方法就是一個會回傳全新物件的方法。在 JS 內我們可以透過實做工廠函式 ( Factory Function )來達成。我們在需要大量生產具有相同屬性的物件時,通常可以利用函式建構子,或是 ES6 後出現的 class 關鍵字:

class Car {
    constructor(options) {
            this.doors = options.doors
          this.state = options.state
          this.color = options.color
    } 
} 

let car1 = new Car({ 
    doors:4, 
  state:'brand new',
    olor:'silver'
})

這也是我們最習慣的新增物件方式。但如果覺得每次都要使用 new 關鍵字會讓程式碼變得複雜,就可以再使用函式把這個 new 的動作包裝起來,這樣就不需要每次都必須要使用 new 運算子,也能夠取得全新產生的物件:

function carFactory(options){
    return new Car(options) 
} 

let car2 = carFactory({ 
    doors:4, 
  state:'brand new',
    color:'silver'
})

上面這樣子回傳新物件的函式又稱為工廠函式,算是 JS 裡最基本工廠方法的實作,剩下就是搭配 JS 的特性做一些變化了,例如我們也可以把原本的工廠函式用另外一個類別的方式來呈現,假設我們有一個交通工具的工廠:

let TransportationOptions = { 
    doors:4, 
  state:'brand new',
    color:'silver'
}

class TransportationFactory{
    constructor(type){
        if (type === 'car'){
             return new Car(TransportationOptions)             
        } 
        if (type === 'bike'){
             return new Bike(TransportationOptions)             
        } 

    } 
} 

let car1 = new TransportationFactory('car')

使用 new 創造物件時在建構函式內如果沒有回傳值,那麼 JS 預設會幫你創造新的物件然後把建構函式內的 this 指向這個新物件;但是如果建構函式回傳另一物件,那這個物件就會取代剛剛說的預設物件成為回傳結果。

利用這個特性我們就可以把類別內的建構函式當成工廠函式,根據建構函式傳入的參數,來決定要產生哪一個物件,看看我們的 TransportationFactory 類別,現在我們有一個統一的介面,卻可以根據不同傳入的參數取得不同物件,這簡直是傑出的一手!

工廠模式對函式建構子或是 class 來說可以算是一種擴充,透過使用工廠方法的方式,我們可以把要新增物件先對要丟給新增物件的類別屬性做先一步的運算,這些邏輯也可以被封裝在工廠類別內,不會影響到其他部分的功能,就像上面這樣,我把一些邏輯判斷放在工廠類別裡面,當作新物件的介面。

動態的工廠類別

上面的例子裡面,我們可以透過交通工具的工廠類別,以bike 跟 car 兩個參數分別取得不同類型的物件,但是這麼一來如果後面我想要動態新增,讓工廠具有能夠創造第三種類別 Truck 的能力,就必須回來修改程式碼,有沒有辦法讓工廠的內容能夠動態被修改呢?

讓我們試試看加入一個註冊的功能,我們把工廠能夠新增的類別內容存放在工廠內,這樣就可以隨時根據已註冊的類別來檢查工廠有沒有創造的能力,我們可以用靜態方法來呈現註冊的功能:

class TransportationFactory{
  static registeredType = new Map() 
    static register (typeName,typeContent) {
            TransportationFactory.registeredType.set(typeName,typeContent)
    } 
} 

搭配 Map 類別就能夠有更語易化的新增修改方式。接下來我們在工廠類別內的函式建構子,就能夠動態去檢查我想要新增的類別,能不能夠過這個工廠產生:

class TransportationFactory{
  static registeredType = new Map() 
    static register (typeName,typeContent) {
            TransportationFactory.registeredType.set(typeName,typeContent)
    } 
    constructor(type,options){
        let typeToCreate = TransportationFactory.registeredType.get(type)  

        if (typeToCreate ){
             return new typeToCreate(options)             
        } else{
            return new Error('Type is not exist!!')
        }
    } 
} 

雖然程式碼顯得有點囉唆,不過這麼一來我們就達成動態改變工廠內容的功能了,讀到這裡你應該可以看出,JS 內的工廠模式其實就是圍繞在工廠函式上,在與其他特性做結合而已,例如知道了上面的實作方式後,除了註冊類別,我們也可以透過 Map,做到註銷的功能,這部分就留給讀者自己練習吧!

總結

這篇文章是設計模式系列的開頭,在這篇文章內我介紹了什麼是設計模式,以及工廠方法,還有負責回傳新物件的工廠函式。我們也了解了 JS 內的工廠方法都是圍繞在工廠函式上去做一些變化,藉此完成了能夠動態修改內容的工廠。希望你讀完這篇文章能夠對工廠模式有基礎的了解,進一步運用在正式開發上囉!

關於筆者

暱稱:Tsai Mujing

文章列表 文章列表