golang設計模式之工廠方法模式

silsuer在掘金發表於2018-11-04

工廠方法模式

wiki: 工廠方法模式(英語:Factory method pattern)是一種實現了“工廠”概念的物件導向設計模式。就像其他建立型模式一樣,它也是處理在不指定物件具體型別的情況下建立物件的問題。工廠方法模式的實質是“定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類。工廠方法讓類的例項化推遲到子類中進行。”

上面是 維基百科 中對工廠方法的定義,在上一篇 golang設計模式之簡單工廠模式 中我們介紹過,唯一的一個工廠控制著 所有產品的例項化,而 工廠方法 中包括一個工廠介面,我們可以動態的實現多種工廠,達到擴充套件的目的

  • 簡單工廠需要:

    1. 工廠結構體
    2. 產品介面
    3. 產品結構體
  • 工廠方法需要:

    1. 工廠介面
    2. 工廠結構體
    3. 產品介面
    4. 產品結構體

簡單工廠 中,依賴於唯一的工廠物件,如果我們需要例項化一個產品,那麼就要向工廠中傳入一個引數獲取對應物件,如果要增加一種產品,就要在工廠中修改建立產品的函式,耦合性過高 ,而在 工廠方法 中,依賴工廠介面,我們可以通過實現工廠介面,建立多種工廠,將物件建立由一個物件負責所有具體類的例項化,變成由一群子類來負責對具體類的例項化,將過程解耦。

下面用程式碼實現:

例如,我們現在有一個產品需要被建立,那麼先構建工廠介面和產品介面


  // 工廠介面
  type FactoryInterface interface {
  	  CreateProduct(t string) ProductInterface
  }

 // 產品介面
 type ProductInterface interface {
 	Intro()
 }

複製程式碼

然後實現這兩個介面: 工廠結構體和產品結構體


  // 建立工廠結構體並實現工廠介面
  type Factory1 struct {
  }

  func (f Factory1) CreateProduct(t string) ProductInterface {
      switch t {
      case "product1":
          return Product1{}
      default:
          return nil
      }

  }


  // 建立產品1並實現產品介面
  type Product1 struct {
  }

  func (p Product1) Intro() {
	  fmt.Println("this is product 1")
  }
複製程式碼

這樣在使用的時候,就可以讓子類來選擇例項化哪種產品:

  // 建立工廠
  	f := new(Factory1)

  	p := f.CreateProduct("product1")
  	p.Intro()  // output:  this is product 1.
複製程式碼

或許上面的程式碼看起來並不容易懂,因為我們只有一種產品,不能看出來它的好處,在網上我看到了一個賣包子的例子,我覺得很貼切,在這我就用go實現一下,輔助理解:

栗子: 我現在想在我的老家齊齊哈爾開一家包子店,賣豬肉餡和三鮮餡兩種餡料的包子,那麼我們使用簡單工廠模式應該怎樣實現呢?

  • 簡單工廠模式實現:

    1. 建立工廠結構體(包子店)

        // 工廠類(包子店)
        type BunShop struct {
        }
      複製程式碼
    2. 建立產品介面(包子類的介面)

        type Bun interface {
        	create()
        }
      複製程式碼
    3. 實現產品(2種包子)

      type PigMeatBuns struct {}
    
      func (p PigMeatBuns) create() {
        fmt.Println("豬肉餡包子")
      }
    
      type SamSunStuffingBuns struct {}
    
      func (s SamSunStuffingBuns) create() {
        fmt.Println("三鮮餡包子")
      }
    複製程式碼
    1. 為工廠新增生產包子的方法
      func (b BunShop) Generate(t string) Bun {
        switch t {
          case "pig":
            return PigMeatBuns{}
          case "3s":
            return SamSunStuffingBuns{}
          default:
            return nil
        }
      }
    複製程式碼
    1. 這樣一個簡單工廠模式就完成了:
    
      factory := new(BunShop)
      b := factory.Generate("pig")
      b.create() // output: 豬肉餡包子
    複製程式碼

可是如果生意做的不錯,我想要在廣東開一家分店該怎麼辦呢?依舊是兩種包子,但是為了符合當地人的口味,一定會有所差別,難道要一步一步的修改工廠類嗎?

這樣工廠方法模式就派上用場了...

  1. 新增工廠介面(包子店的介面)和產品介面(包子介面)

      type BunShopInterface interface{
         Generate(t string) Bun
      }
    
      type Bun interface {
         create()
      }
    複製程式碼
  2. 建立工廠結構體和產品結構體(具體包子店和具體包子)

      type QSPigMeatBuns struct{}
      type GDPigMeatBuns struct{}

      type QSSamSunStuffingBuns struct{}
      type GDSamSunStuffingBuns struct{}

      // 實現產品介面...  這裡就不寫了
      // CODE ...
複製程式碼
  1. 建立對應的工廠(齊市包子店和廣東包子店)
    type QSBunShop struct {}

    type GDBunShop struct {}

    func (qs QSBunShop) Generate(t string) Bun {
       switch t {
           case "pig":
             return QSPigMeatBuns{}
           case "3s":
             return QSSamSunStuffingBuns{}
           default:
             return nil
        }
    }


    func (gd QSBunShop) Generate(t string) Bun {
         switch t {
           case "pig":
             return GDPigMeatBuns{}
           case "3s":
             return GDSamSunStuffingBuns{}
           default:
             return nil
           }
      }
複製程式碼
  1. 這樣,就完成了工廠方法模式
      var b Bun
      // 賣呀賣呀賣包子...
      QSFactory := new(QSBunShop)
      b = QSFactory.Generate("pig")  // 傳入豬肉餡的引數,會返回齊市包子鋪的豬肉餡包子
      b.create()

      GDFactory := new(GDBunShop)
      b = GDFactory.Generate("pig") // 同樣傳入豬肉餡的引數,會返回廣東包子鋪的豬肉餡包子
      b.create()
複製程式碼

go中沒有繼承,實際上可以以組合的方式達到繼承的目的

簡單工廠模式和工廠方法模式看起來很相似,本質區別就在於,如果在包子店中直接建立包子產品,是依賴具體包子店的,擴充套件性、彈性、可維護性都較差,而如果將例項化的程式碼抽象出來,不再依賴具體包子店,而是依賴於抽象的包子介面,使物件的實現從使用中解耦,這樣就擁有很強的擴充套件性了,也可以稱為 『依賴倒置原則』

工廠方法模式的優缺點

  • 優點:

    1. 符合“開閉”原則,具有很強的的擴充套件性、彈性和可維護性。修改時只需要新增對應的工廠類即可

    2. 使用了依賴倒置原則,依賴抽象而不是具體,使用(客戶)和實現(具體類)鬆耦合

    3. 客戶只需要知道所需產品的具體工廠,而無須知道具體工廠的建立產品的過程,甚至不需要知道具體產品的類名。

  • 缺點:

    1. 每增加一個產品時,都需要一個具體類和一個具體建立者,使得類的個數成倍增加,導致系統類數目過多,複雜性增加

    2. 對簡單工廠,增加功能修改的是工廠類;對工廠方法,增加功能修改的是產品類。

上述程式碼均放在 golang-design-patterns 這個倉庫中

打個廣告,推薦一下自己寫的 go web框架 bingo,求star,求PR ~

相關文章