iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0

今天要來聊聊 Creational Patten 當中的工廠模式。

當我們透過類別建立出實例的時候,其實感覺就像是一個工廠生產出了產品。而同一個工廠 (類別),可以生產出無限多個同樣者產品 (實例)。

回到我們先前的例子,這裡的 BaseballPlayerTennisPlayer 類別繼承了 Athlete,並能夠各自產出各自的實例,並且針對 hit 方法有不同的實作方式。

abstract class Athlete {
  constructor() {}

  abstract hit(): void;
}

class BaseballPlayer extends Athlete {
  hit() {
    console.log('Baseball player can hit baseball')
  }
}

class TennisPlayer extends Athlete {
  hit() {
    console.log('Tennis player can hit tennis')
  }
}

const jeter = new BaseballPlayer()
const federer = new TennisPlayer()

jeter.hit()      // Baseball player can hit baseball
federer.hit()    // Tennis player can hit tennis

但如果我們希望能夠有個 AthleteFactory,像是中央生產工廠,不管我想要 baseball player 還是 tennis player,找它就可以生產了

簡單工廠模式

於是,這裡就出現了一個簡單的解法:建立一個靜態類別AthleteFactory,並且有一個靜態方法 trainAthlete,可以根據使用者的輸入,來判斷要提供什麼樣的產品

class AthleteFactory {
  static trainAthlete(category: string): Athlete {
    switch (category) {
      case 'baseball':
        return new BaseballPlayer()
      case 'tennis':
        return new TennisPlayer()
      default:
        return null
    }
  }
}

所以如果我想要一位 baseball player,就在呼叫方法的時候,輸入 'baseball'。想要一位 tennis player 的時候,就輸入 'tennis',就能得到期待中的結果。

const ohtani = AthleteFactory.trainAthlete('baseball')
const nadal = AthleteFactory.trainAthlete('tennis')

ohtani.hit()   // Baseball player can hit baseball
nadal.hit()    // Tennis player can hit tennis

另一方面,如果想要持續增加不同的產品,只要在 AthleteFactory 當中持續擴充 switch 區塊就行!

簡單工廠模式的優缺點

使用者能夠快速從工廠取出需要的實例來使用,不需要自己去找目標類別來建立實例。另一方面,使用者也不需要管這些實例是怎麼被建立的,只要負責使用就行。

簡單工廠的實作方式簡單好懂,能夠快速上手(平常可能不知不覺就採用了簡單工廠模式)。

不過缺點就是,如果要新增一個新的商品,就必須要回去修改 AthleteFactory 當中的程式碼,有可能會影響到舊有的程式碼。另一方面,簡單工廠集中了所有建立實例的邏輯與責任,一旦不能正常運作,那麼所有產品都會受到影響。

工廠模式

所以,與其讓某個實際的工廠擁有所有生產產品的邏輯,不如就讓每個產品有各自的工廠,但卻有遵守相同的生產方式。

所以在工廠模式中,沒有一個中央生產的工廠,只有一個指導其他工廠如何運作的抽象介面,像是下面的 AthleteFactory

interface AthleteFactory {
  trainAthlete(): Athlete;
}

接著,我們分別位 baseball player 和 tennis player 建立 BaseballPlayerFactoryTennisPlayerFactory 工廠,並且執行 AthleteFactory 介面

class BaseballPlayerFactory implements AthleteFactory {
  constructor() {}

  trainAthlete() {
    return new BaseballPlayer()
  }
}

class TennisPlayerFactory implements AthleteFactory {
  constructor() {}

  trainAthlete() {
    return new TennisPlayer()
  }
}

最後,如果我們希望生產 baseball player,就建立一個 baseballPlayerFactory;如果我們希望生產 tennis player,就建立一個 tennisPlayerFactory

const baseballPlayerFactory = new BaseballPlayerFactory()
const tennisPlayerFactory = new TennisPlayerFactory()

接著,兩者可以用同樣的方式,建立需要的實例,得到預期中的結果

const ohtani = baseballPlayerFactory.trainAthlete()
const nadal = tennisPlayerFactory.trainAthlete()

ohtani.hit()    // Baseball player can hit baseball
nadal.hit()     // Tennis player can hit tennis

解決什麼問題?

有些時候我們因應外在環境的變動,需要產出類似的、但是又截然不同的實例(譬如棒球選手和網球選手),但又要避免像簡單工廠那樣,因集中生產所造成的維護和擴充問題,最後這裡我們透過「抽象」的方式來解決問題。

首先是建立一個介面(或是類別也可以)來定義各個工廠的實作要求 (譬如 trainAthlete 方法),但是不定義詳細的實作方式。

接著,讓不同的產品建立相對應不同的工廠,並各自執行 trainAthlete 方法。譬如 BaseballPlayerFactory 的做法就是呼叫 new BaseballPlayer()

最後,使用者根據需求,呼叫需要的工廠來生產實例。

優點與缺點

相對於簡單工廠模式,工廠模式的好處是滿足了「單一功能原則」,讓 AthleteFactory 只管要有什麼方法,而不用管實作細節;也滿足了「開放封閉原則」,未來有任何新的需求出現,只要按照介面(或類別)設計新的工程即可,大大提升了維護性和擴充性。

不過缺點就是,每當有新需求出現,我們就需要建立一個新的工廠 (e.g., xxxFactory),以及負責生產實例的類別,兩者會成雙成對出現。


上一篇
Singleton 單例模式
下一篇
Abstract Factory 抽象工廠模式
系列文
幫自己搞懂物件導向和設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Ken Chen
iT邦新手 4 級 ‧ 2021-10-17 21:25:09

感謝分享欸,滿喜歡這系列的文章,趕完鐵人終於可以來細細品嚐了

我要留言

立即登入留言