iT邦幫忙

2022 iThome 鐵人賽

DAY 10
1
自我挑戰組

【從工程師升級成為資深工程師的那檔事】 系列 第 10

【從工程師升級成為資深工程師的那檔事 Day10】 設計模式 - 工廠模式

  • 分享至 

  • xImage
  •  

這邊介紹到的工廠模式,主要會分成兩種工廠模式來討論

  • 簡單工廠 (Simple Factory)
  • 抽象工廠 (Abstract Factory)

工廠模式 Factory Pattern

定義

透過特定的類別(Class)來創建物件(Object)

用途

透過特定類別來完成物件的實例化,可讓高層模組與低層模組分離達到依賴反轉(DIP)的效果。
(並且高階模組不參與建立的過程)

通常會用在建立有固定初始化步驟的物件上。
較常運用到的場景像是將資料轉成物件(DTO)的時候,可能就會用到。

範例

假設有個需求,需要我們將Log資料抓出來並顯示其提供的內容給使用者看,
並且Log資料有分Erro類型、Debug類型、Info類型,
這時我們應該要如何設計?

簡單工廠模式 Simple Factory

//建立一個ILog的介面
interface ILog{
  public string show();
}

//分別建立 ErroLog、DebugLog、InfoLog 實作 ILog
class ErroLog : ILog{
  public string show(){
    //回傳某些資料
  }
}

class DebugLog : ILog{
  public string show(){
    //回傳某些資料
  }
}

class InfoLog : ILog{
  public string show(){
    //回傳某些資料
  }
}
//設計一個簡單工廠的類別
Class SimpleFactory{
  public ILog createLogObject(string type){
    switch(type){
      case "erro":
        return new ErroLog();
      case "debug":
        return new DebugLog();
      case "info":
        return new InfoLog();
    }
  }
}

//最後設計一下主流程 main

public static void main(){
  //創建工廠
  var factory = new SimpleFactory();
  
  //這時候我們並不會知道他會回傳甚麼樣的物件(只知道是ILog的子類別)
  //上層只知道傳入不同的字串(erro、debug、info)就能得到想要的答案
  ILog log;
  
  //取得ErroLog訊息
  log = factory.createLogObject("erro");
  Console.WriteLine(log.show())
  
  //取得DebugLog訊息
  log = factory.createLogObject("debug");
  Console.WriteLine(log.show())
  
  //取得InfoLog訊息
  log = factory.createLogObject("info");
  Console.WriteLine(log.show())
  
}

這樣的設計方式雖然簡單方便,但如果今天發生了一個狀況...

當我們興致沖沖的把這隻程式交付給客戶的時候,客戶突然提一個需求說
"經理不喜歡看到錯誤訊息,如果是經理來看的時候幫我將ErroLog的訊息改成InfoLog"
我們當然可以直接在createLogObject()做修改

public ILog createLogObject(string type){
    switch(type){
      case "erro":
        return new ErroLog();
      case "debug":
        return new DebugLog();
      case "info":
        return new InfoLog();
    }
  }

改成

public ILog createLogObject(string type,bool isManager){
    switch(type){
      case "erro":
        if(isManger){
          return new InfoLog();
        }
        else{
          return new ErroLog();
        }
      case "debug":
        return new DebugLog();
      case "info":
        return new InfoLog();
    }
  }

以這種方式完成,當然沒問題。
但我們會發現原本的結構被破壞了,甚至還要多一個參數傳進來。

抽象工廠 Abstract Factory

在前面敘述的例子中會發現,每當我們有新的需求增加時,
都必須回去修改 SimpleFactory這支類別。
那我們應該如何解決這個問題呢?

//先設計一個工廠的抽象類別(或是介面)
interface IFactory{
  public ILog createLogObject(string type);
}
//設計兩個實作的工廠(ConcreteFactory)一個是LogFactory、另一個是ManagerFactory
class LogFactory{
  public ILog createLogObject(string type){
    switch(type){
      case "erro":
        return new ErroLog();
      case "debug":
        return new DebugLog();
      case "info":
        return new InfoLog();
    }
  }
}

class ManagerLogFactory{
  public ILog createLogObject(string type){
    switch(type){
      case "erro":
        return new InfoLog();
      case "debug":
        return new DebugLog();
      case "info":
        return new InfoLog();
    }
  }
}

//最後重新設計主流程 main

public static void main(){
  var isManager = true;
  //宣告工廠
  IFactory factory;
  
  //創建工廠(根據是否是經理來決定創建哪一種工廠)
  if(isManager){
    factory = new ManagerLogFactory();
  }else{
    factory = new LogFactory();
  }
  
  //後半段與簡單工廠就一致了
  ILog log;
  
  //取得ErroLog訊息
  log = factory.createLogObject("erro");
  Console.WriteLine(log.show())
  
  //取得DebugLog訊息
  log = factory.createLogObject("debug");
  Console.WriteLine(log.show())
  
  //取得InfoLog訊息
  log = factory.createLogObject("info");
  Console.WriteLine(log.show())
  
}

結語

工廠的本質是用來實現高階與低階模組解耦的設計,
所以在這邊就沒有特別探討工廠方法(Factory Method),
一方面在定義上他不太符合SRP原則,另一方面實用性也不是那麼大。

雖然抽象工廠的設計方式,讓工廠模式可以有更高自由度的設計。
但是工廠模式的兩大核心目標是解耦跟創建,
所以在使用的時候並不建議無限上綱的在工廠模式中加上更多維度的設計。


上一篇
【從工程師升級成為資深工程師的那檔事Day 9】設計模式 - 單例模式
下一篇
【從工程師升級成為資深工程師的那檔事Day 11】設計模式 - 生成器模式
系列文
【從工程師升級成為資深工程師的那檔事】 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言