工廠模式有三個 Level,你能用 Go 寫到第幾層?
設計模式中的工廠模式是我們編寫代碼時常用的一種建造型模式,用於創建指定類的實例。
在不使用設計模式的時候,我們是怎麼創建類的實例的呢?
別多想,這個問題沒坑,就是我們寫代碼時直接用 new 關鍵字,直接創建實例。比如 Java 語言裏是通過 new 關鍵字直接調用類的構造方法,完成實例的創建。
class Person {}
Person p1 = new Person();
而像 Go 語言這類,雖說是非面嚮對象語言,但也提供了創建類型實例指針的 new 方法。
type Person struct{}
var p1 Person
p1 = new(Person)
那既然能直接 new ,有人肯定會問 “那爲什麼還要用設計模式啊?而且聽說工廠模式裏邊還能分出好幾種工廠”。解答這個問題並不難,因爲我們總是會把一些已經定型的業務處理邏輯抽象成各種項目裏的公共類庫,這樣才能找到抓手,讓產品開發形成閉環......。
剛纔說到了把一些執行流程明確、但流程細節有差異的業務處理邏輯抽象成公共類庫。這時候一組具有相同行爲的類都會實現一個固定流程的接口,但是程序裏該創建哪個類的實例,提前不知道,只能是根據運行參數動態判斷,在加上對於類實例化的過程我們可能需要收斂一下,這樣才能保證生產出來的實例能符合我們的預期。
於是乎,聰明的你一定會想到,這時候,我讓類庫再暴露出一個 NewXXX 之類的方法,這個 NewXXX 方法能夠根據條件生產出具體類型的實例返回給業務程序用。如果你能想到這裏,恭喜你,這個時候你已經用上工廠模式了。
有時候就是這樣,在寫代碼的時候,不知不覺地就用上設計模式了,雖然你可能還不知道。等我們把設計模式用熟後,自然就能有意識地寫出更好的代碼。
設計模式裏工廠模式一共可以提煉成三類工廠:
-
簡單工廠
-
工廠方法
-
抽象工廠
這三類工廠模式的抽象度依次提高,像上面給大家舉的例子,你不知不覺用上的就是 “簡單工廠” 這種設計模式,隨着這種流程定型的類庫越來越複雜,要求的抽象度變高後,我們可能還會用到後面兩種工廠模式。這裏我就用一些例子,把這三種工廠模式給大家分析一下。
簡單工廠
Go 語言沒有構造函數一說,所以一般會定義 NewXXX 函數來初始化相關類。NewXXX 函數返回接口時就是簡單工廠模式。
考慮一個簡單的應用場景,這個應用場景裏會提供很多語言的打印機,他們都源於一個 Printer 接口。
// Printer 簡單工廠要返回的接口類型
type Printer interface {
Print(name string) string
}
程序通過簡單工廠向客戶端提供需要的語種的打印機。
func NewPrinter(lang string) Printer {
switch lang {
case "cn":
return new(CnPrinter)
case "en":
return new(EnPrinter)
default:
return new(CnPrinter)
}
}
目前這個場景裏我們先提供兩個語種的打印機,他們都是 Printer 接口的具體實現類型。
// CnPrinter 是 Printer 接口的實現,它說中文
type CnPrinter struct {}
func (*CnPrinter) Print(name string) string {
return fmt.Sprintf("你好, %s", name)
}
// EnPrinter 是 Printer 接口的實現,它說中文
type EnPrinter struct {}
func (*EnPrinter) Print(name string) string {
return fmt.Sprintf("Hello, %s", name)
}
這個場景下,工廠、產品接口、具體產品類的關係可以用下面這個圖表示。
"示例源碼運行Demo
給公衆號「網管叨bi叨」發送 go-factory 即可領取"
printer := NewPrinter("en")
fmt.Println(printer.Print("Bob"))
簡單工廠模式主要包含 3 個角色。
-
簡單工廠:是簡單工廠模式的核心,負責實現創建所有實例的內部邏輯。工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。
-
抽象產品:是簡單工廠創建的所有對象的抽象父類 / 接口,負責描述所有實例的行爲。
-
具體產品:是簡單工廠模式的創建目標。
簡單工廠的優點是,簡單,缺點嘛,如果具體產品擴產,就必須修改工廠內部,增加 Case,一旦產品過多就會導致簡單工廠過於臃腫,爲了解決這個問題,纔有了下一級別的工廠模式 -- 工廠方法。
工廠方法
工廠方法模式(Factory Method Pattern)又叫作多態性工廠模式,指的是定義一個創建對象的接口,但由實現這個接口的工廠類來決定實例化哪個產品類,工廠方法把類的實例化推遲到子類中進行。
在工廠方法模式中,不再由單一的工廠類生產產品,而是由工廠類的子類實現具體產品的創建。因此,當增加一個產品時,只需增加一個相應的工廠類的子類, 以解決簡單工廠生產太多產品時導致其內部代碼臃腫(switch … case 分支過多)的問題。
下面舉個簡單的例子來理解工廠方法的設計思想,考慮有這樣一個生產計算器的工廠,每類計算器產品都由一個子工廠負責生產。
注意:Go 中沒有繼承,所以這裏說的工廠子類,其實是直接實現工廠接口的具體工廠類。
// OperatorFactory 工廠接口,由具體工廠類來實現
type OperatorFactory interface {
Create() MathOperator
}
// MathOperator 實際產品實現的接口--表示數學運算器應該有哪些行爲
type MathOperator interface {
SetOperandA(int)
SetOperandB(int)
ComputeResult() int
}
現在我們假定程序可以生產兩類計算器,加法計算器和乘法計算器,也就是在工廠方法模式中,存在兩個子類工廠。
//PlusOperatorFactory 是 PlusOperator 加法運算器的工廠類
type PlusOperatorFactory struct{}
func (pf *PlusOperatorFactory) Create() MathOperator {
return &PlusOperator{
BaseOperator: &BaseOperator{},
}
}
// MultiOperatorFactory 是乘法運算器產品的工廠
type MultiOperatorFactory struct {}
func (mf *MultiOperatorFactory) Create() MathOperator{
return &MultiOperator{
BaseOperator: &BaseOperator{},
}
}
這兩個子類工廠分別用來生產加法和乘法計算器,
注意:這裏爲了理解,例子都很簡單,真實場景下每個子類工廠創建產品實例的時候是可以放進去複雜邏輯的,不是簡單的 New 一下。
// BaseOperator 是所有 Operator 的基類
// 封裝公用方法,因爲Go不支持繼承,具體Operator類
// 只能組合它來實現類似繼承的行爲表現。
type BaseOperator struct {
operandA, operandB int
}
func (o *BaseOperator) SetOperandA(operand int) {
o.operandA = operand
}
func (o *BaseOperator) SetOperandB(operand int) {
o.operandB = operand
}
//PlusOperatorFactory 是 PlusOperator 加法運算器的工廠類
type PlusOperatorFactory struct{}
func (pf *PlusOperatorFactory) Create() MathOperator {
return &PlusOperator{
BaseOperator: &BaseOperator{},
}
}
//PlusOperator 實際的產品類--加法運算器
type PlusOperator struct {
*BaseOperator
}
//ComputeResult 計算並獲取結果
func (p *PlusOperator) ComputeResult() int {
return p.operandA + p.operandB
}
// MultiOperatorFactory 是乘法運算器產品的工廠
type MultiOperatorFactory struct {}
func (mf *MultiOperatorFactory) Create() MathOperator{
return &MultiOperator{
BaseOperator: &BaseOperator{},
}
}
// MultiOperator 實際的產品類--乘法運算器
type MultiOperator struct {
*BaseOperator
}
func (m *MultiOperator) ComputeResult() int {
return m.operandA * m.operandB
}
這個場景下,工廠、產品接口、具體產品類的關係可以用下面這個圖表示。
類圖 -- 工廠方法
測試運行 -- 客戶端使用子類工廠創建產品實例。
// 測試運行
示例源碼運行Demo
給公衆號「網管叨bi叨」發送 go-factory 即可領取
func main() {
var factory OperatorFactory
var mathOp MathOperator
factory = &PlusOperatorFactory{}
mathOp = factory.Create()
mathOp.SetOperandB(3)
mathOp.SetOperandA(2)
fmt.Printf("Plus operation reuslt: %d\n", mathOp.ComputeResult())
factory= &MultiOperatorFactory{}
mathOp = factory.Create()
mathOp.SetOperandB(3)
mathOp.SetOperandA(2)
fmt.Printf("Multiple operation reuslt: %d\n", mathOp.ComputeResult())
}
工廠方法模式的優點
-
靈活性增強,對於新產品的創建,只需多寫一個相應的工廠類。
-
典型的解耦框架。高層模塊只需要知道產品的抽象類,無須關心其他實現類,滿足迪米特法則、依賴倒置原則和里氏替換原則。
工廠方法模式的缺點
-
類的個數容易過多,增加複雜度。
-
增加了系統的抽象性和理解難度。
-
只能生產一種產品,此弊端可使用抽象工廠模式解決。
無論是簡單工廠還是工廠方法都只能生產一種產品,如果工廠需要創建生態裏的多個產品,就需要更進一步,使用第三級的工廠模式 -- 抽象工廠。
抽象工廠
抽象工廠模式:用於創建一系列相關的或者相互依賴的對象。
爲了更清晰地理解工廠方法模式和抽象工廠模式的區別,我們舉一個品牌產品生態的例子。
比如智能家居領域多家公司,現在有華爲和小米,他們的工廠除了生產我們熟知的手機外,還會生產電視、空調這種家電設備。
假如我們有幸作爲他們工廠智能化管理軟件的供應商,在軟件系統裏要對工廠進行抽象,這個時候就不能再用工廠方法這種設計模式了,因爲工廠方法只能用來生產一種產品。
我們先看一下使用類圖表示的這個抽象工廠抽象多品牌 -- 多產品的形態。
類圖 -- 抽象工廠
下面我們用代碼簡單實現一個抽象工廠,這個工廠能生成智能電視和空調,當然產品的功能在代碼裏比較簡單,就是輸出一條相應的信息。
目前抽象工廠有兩個實際工廠類一個是華爲的工廠,一個是小米的工廠,他們用來實際生產自家的產品設備。
// 示例源碼運行Demo
// 給公衆號「網管叨bi叨」發送 go-factory 即可領取
type AbstractFactory interface {
CreateTelevision() ITelevision
CreateAirConditioner() IAirConditioner
}
type ITelevision interface {
Watch()
}
type IAirConditioner interface {
SetTemperature(int)
}
type HuaWeiFactory struct{}
func (hf *HuaWeiFactory) CreateTelevision() ITelevision {
return &HuaWeiTV{}
}
func (hf *HuaWeiFactory) CreateAirConditioner() IAirConditioner {
return &HuaWeiAirConditioner{}
}
type HuaWeiTV struct{}
func (ht *HuaWeiTV) Watch() {
fmt.Println("Watch HuaWei TV")
}
type HuaWeiAirConditioner struct{}
func (ha *HuaWeiAirConditioner) SetTemperature(temp int) {
fmt.Printf("HuaWei AirConditioner set temperature to %d ℃\n", temp)
}
type MiFactory struct{}
func (mf *MiFactory) CreateTelevision() ITelevision {
return &MiTV{}
}
func (mf *MiFactory) CreateAirConditioner() IAirConditioner {
return &MiAirConditioner{}
}
type MiTV struct{}
func (mt *MiTV) Watch() {
fmt.Println("Watch HuaWei TV")
}
type MiAirConditioner struct{}
func (ma *MiAirConditioner) SetTemperature(temp int) {
fmt.Printf("Mi AirConditioner set temperature to %d ℃\n", temp)
}
func main() {
var factory AbstractFactory
var tv ITelevision
var air IAirConditioner
factory = &HuaWeiFactory{}
tv = factory.CreateTelevision()
air = factory.CreateAirConditioner()
tv.Watch()
air.SetTemperature(25)
factory = &MiFactory{}
tv = factory.CreateTelevision()
air = factory.CreateAirConditioner()
tv.Watch()
air.SetTemperature(26)
}
同樣抽象工廠也具備工廠方法把產品的創建推遲到工廠子類去做的特性,假如未來加入了 VIVO 的產品,我們就可以通過再創建 VIVO 工廠子類來擴展。
對於抽象工廠我們可以總結以下幾點:
-
當系統所提供的工廠所需生產的具體產品並不是一個簡單的對象,而是多個位於不同產品等級結構中屬於不同類型的具體產品時需要使用抽象工廠模式。
-
抽象工廠模式是所有形式的工廠模式中最爲抽象和最具一般性的一種形態。
-
抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品對象的創建 。
-
當一個工廠等級結構可以創建出分屬於不同產品等級結構的一個產品族中的所有對象時,抽象工廠模式比工廠方法模式更爲簡單、有效率。
抽象工廠模式的優點
-
當需要產品族時,抽象工廠可以保證客戶端始終只使用同一個產品的產品族。
-
抽象工廠增強了程序的可擴展性,對於新產品族的增加,只需實現一個新的具體工廠即可,不需要對已有代碼進行修改,符合開閉原則。
抽象工廠模式的缺點
-
規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口。
-
增加了系統的抽象性和理解難度。
最後
我們用幾個較爲簡單的例子和大家一起學習了下三個工廠模式各自的場景和優缺點,實際使用的時候項目一開始需求還沒那麼明確的時候推薦還是先用簡單工廠,等我們業務理解更透徹後如果確實需要再升級到工廠方法也不遲。
抽象工廠也是,如果確定引入產品生態的概念才能更好地進行領域建模,再開始使用抽象工廠也不遲。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/MlC6-TDf06LGpF8hxcSV_w