1. 程式人生 > >[Design Pattern With Go]設計模式-工廠模式

[Design Pattern With Go]設計模式-工廠模式

這次介紹的設計模式是工廠模式,這是一個比較常見的建立型模式。一般情況下,工廠模式分為三種:簡單工廠、工廠方法和抽象工廠,下面慢慢舉例介紹下。 # 簡單工廠 考慮一個加密程式的應用場景,一個加密程式可能提供了AES,DES等加密方法,這些加密方式都實現了同一個介面ICipher,它有兩個方法分別是 Encript 和 Decript。我們使用加密程式的時候會希望簡單的指定加密方式,然後傳入原始資料以及必要引數,然後就能得到想要的加密資料。這個功能用簡單工廠如何實現呢? ## 模式結構 簡單工廠模式包含一下幾個角色: - Factory(工廠角色),負責建立所有例項。 - Product(抽象產品角色),指工廠所建立的例項的基類,在 golang 中通常為介面。 - ConcreteProduce(具體產品),指工廠所建立的具體例項的型別。 在這個加密程式的例子中,工廠角色的職責是返回加密函式;抽象產品角色是所有加密類的基類,在 golang 中是定義了加密類通用方法的介面;具體產品是指具體的加密類,如 AES、DES 等等。我們可以用 UML 關係圖來表示這幾個角色之間的關係: ![](https://raw.githubusercontent.com/LooJee/medias/master/images/20210329101034.png) ## 程式碼設計 依據 UML 關係圖,我們可以設計出採用簡單工廠模式的加密程式碼。首先是 ICipher 介面,定義了 Encript 和 Decript 兩個方法: ``` golang type ICipher interface { Encrypt([]byte) ([]byte, error) Decrypt([]byte) ([]byte, error) } ``` 然後根據這個介面,分別實現 AESCipher 和 DESCipher 兩個加密類。 **AESCipher**: ``` golang type AESCipher struct { } func NewAESCipher() *AESCipher { return &AESCipher{} } func (c AESCipher) Encrypt(data []byte) ([]byte, error) { return nil, nil } func (c AESCipher) Decrypt(data []byte) ([]byte, error) { return nil, nil } ``` **DESCipher**: ```golang type DESCipher struct { } func NewDesCipher() *DESCipher { return &DESCipher{} } func (c DESCipher) Encrypt(data []byte) ([]byte, error) { return nil, nil } func (c DESCipher) Decrypt(data []byte) ([]byte, error) { return nil, nil } ``` 最後是一個工廠角色,根據傳入的引數返回對應的加密類,Java 需要實現一個工廠類,這裡我們用一個函式來做加密類工廠: ```golang func CipherFactory(cType string) ICipher { switch cType { case "AES": return NewAESCipher() case "DES": return NewDesCipher() default: return nil } } ``` 這樣,通過呼叫 CipherFactory 傳入所需的加密型別,就可以得到所需要的加密類例項了。 ```golang func TestCipherFactory(t *testing.T) { c := CipherFactory("RSA") if c != nil { t.Fatalf("unsupport RSA") } c = CipherFactory("AES") if reflect.TypeOf(c) != reflect.TypeOf(&AESCipher{}) { t.Fatalf("cipher type should be AES") } c = CipherFactory("DES") if reflect.TypeOf(c) != reflect.TypeOf(&DESCipher{}) { t.Fatalf("cipher type should be DES") } } ``` ## 小結 簡單工廠將業務程式碼和建立例項的程式碼分離,使職責更加單一。不過,它將所有建立例項的程式碼都放到了 CipherFactory 中,當加密類增加的時候會增加工廠函式的複雜度,產品型別增加時需要更新工廠函式這一操作也是違反了“開閉原則”,所以簡單工廠更適合負責建立的物件比較少的場景。 # 工廠方法 為了讓程式碼更加符合“開閉原則”,我們可以給每個產品都增加一個工廠子類,每個子類生成具體的產品例項,將工廠方法化,也就是現在要介紹的工廠方法模式。 ## 模式結構 工廠方法和和簡單工廠相比,將工廠角色細分成抽象工廠和具體工廠: - Product(抽象產品):定義產品的介面。 - ConcreteFactory(具體產品):具體的產品例項。 - Factory(抽象工廠):定義工廠的介面。 - ConcreteFactory(具體工廠):實現抽象工廠,生產具體產品。 可以使用如下的 UML 圖來表示這幾個角色直接的關係: ![](https://raw.githubusercontent.com/LooJee/medias/master/images/20210329142446.png) ## 程式碼設計 抽象產品角色和具體產品角色就不再定義了,和簡單工廠相同,具體展示一下抽象工廠角色和具體工廠角色。 抽象工廠角色定義了一個方法,用於建立對應的產品: ```golang type ICipherFactory interface { GetCipher() ICipher } ``` 根據這個介面,分別定義出 AESCipherFactory、和 DESCipherFactory 兩個子類工廠。 **AESCipherFactory** ```golang type AESCipherFactory struct { } func (AESCipherFactory) GetCipher() ICipher { return NewAESCipher() } func NewAESCipherFactory() *AESCipherFactory { return &AESCipherFactory{} } ``` **DESCipherFactory** ```golang type DESCipherFactory struct { } func (DESCipherFactory) GetCipher() ICipher { return NewDESCipher() } func NewDESCipherFactory() *DESCipherFactory { return &DESCipherFactory{} } ``` 然後編寫一個單元測試來檢驗我們的程式碼: ```golang func TestCipherFactory(t *testing.T) { var f ICipherFactory = NewAESCipherFactory() if reflect.TypeOf(f.GetCipher()) != reflect.TypeOf(&AESCipher{}) { t.Fatalf("should be AESCipher") } f = NewDESCipherFactory() if reflect.TypeOf(f.GetCipher()) != reflect.TypeOf(&DESCipher{}) { t.Fatalf("should be DESCipher") } } ``` ## 小結 在工廠方法模式中,定義了一個工廠介面,然後根據各個產品子類定義實現這個介面的子類工廠,通過子類工廠來返回產品例項。這樣修改建立例項程式碼只需要修改子類工廠,新增例項時只需要新增具體工廠和具體產品,而不需要修改其它程式碼,符合“開閉原則”。不過,當具體產品較多的時候,系統中類的數量也會成倍的增加,一定程度上增加了系統的複雜度。而且,在實際使用場景中,可能還需要使用反射等技術,增加了程式碼的抽象性和理解難度。 # 抽象工廠 下面再用加密這個例子可能不太好,不過我們假設需求都合理吧。現在需求更加細化了,分別需要 64 位 key 和 128 位 key 的 AES 加密庫以及 64 位 key 和 128 位 key 的 DES 加密庫。如果使用工廠方法模式,我們一共需要定義 4 個具體工廠和 4 個具體產品。 ```golang AESCipher64 AESCipher128 AESCipherFactory64 AESCipherFactory128 DESCipher64 DESCipher128 DESCipherFactory64 DESCipherFactory128 ``` 這時候,我們可以把有關聯性的具體產品組合成一個產品組,例如AESCipher64 和 AESCipher128,讓它們通過同一個工廠 AESCipherFactory 來生產,這樣就可以簡化成 2 個具體工廠和 4 個具體產品 ```golang AESCipher64 AESCipher128 AESCipherFactory DESCipher64 DESCipher128 DESCipherFactory ``` 這就是抽象工廠模式。 ## 模式結構 抽象工廠共有 4 個角色: - AbstractFactory(抽象工廠):定義工廠的介面。 - ConcreteFactory(具體工廠):實現抽象工廠,生產具體產品。 - AbstractProduct(抽象產品):定義產品的介面。 - Product(具體產品):具體的產品例項。 根據角色定義我們可以畫出抽象工廠的 UML 關係圖: ![](https://raw.githubusercontent.com/LooJee/medias/master/images/20210329162259.png) ## 程式碼設計 抽象產品和具體產品的定義與工廠方法類似: **抽象產品**: ```golang type ICipher interface { Encrypt(data, key[]byte) ([]byte, error) Decrypt(data, key[]byte) ([]byte, error) } ``` **AESCipher64**: ```golang type AESCipher64 struct { } func NewAESCipher64() *AESCipher64 { return &AESCipher64{} } func (AESCipher64) Encrypt(data, key []byte) ([]byte, error) { return nil, nil } func (AESCipher64) Decrypt(data, key []byte) ([]byte, error) { return nil, nil } ``` **AESCipher128**: ``` golang type AESCipher128 struct { } func NewAESCipher128() *AESCipher128 { return &AESCipher128{} } func (AESCipher128) Encrypt(data, key []byte) ([]byte, error) { return nil, nil } func (AESCipher128) Decrypt(data, key []byte) ([]byte, error) { return nil, nil } ``` **AESCipher128**: ```golang type c struct { } func NewDESCipher64() *DESCipher64 { return &DESCipher64{} } func (DESCipher64) Encrypt(data, key []byte) ([]byte, error) { return nil, nil } func (DESCipher64) Decrypt(data, key []byte) ([]byte, error) { return nil, nil } ``` **DESCipher128**: ```golang type DESCipher128 struct { } func NewDESCipher128() *DESCipher128 { return &DESCipher128{} } func (DESCipher128) Encrypt(data, key []byte) ([]byte, error) { return nil, nil } func (DESCipher128) Decrypt(data, key []byte) ([]byte, error) { return nil, nil } ``` 抽象工廠角色和工廠方法相比需要增加 GetCipher64 和 GetCipher128 兩個方法定義: ```golang type ICipherFactory interface { GetCipher64() ICipher GetCipher128() ICipher } ``` 然後分別實現 AESCipherFactory 和 DesCipherFactory 兩個具體工廠: **AESCipherFactory**: ```golang type AESCipherFactory struct { } func (AESCipherFactory) GetCipher64() ICipher { return NewAESCipher64() } func (AESCipherFactory) GetCipher128() ICipher { return NewAESCipher128() } func NewAESCipherFactory() *AESCipherFactory { return &AESCipherFactory{} } ``` **DESCipherFactory**: ```golang type DESCipherFactory struct { } func (DESCipherFactory) GetCipher64() ICipher { return NewDESCipher64() } func (DESCipherFactory) GetCipher128() ICipher { return NewDESCipher128() } func NewDESCipherFactory() *DESCipherFactory { return &DESCipherFactory{} } ``` 編寫單元測試驗證我們的程式碼: ```golang func TestAbstractFactory(t *testing.T) { var f = NewCipherFactory("AES") if reflect.TypeOf(f.GetCipher64()) != reflect.TypeOf(&AESCipher64{}) { t.Fatalf("should be AESCipher64") } if reflect.TypeOf(f.GetCipher128()) != reflect.TypeOf(&AESCipher128{}) { t.Fatalf("should be AESCipher128") } f = NewCipherFactory("DES") if reflect.TypeOf(f.GetCipher64()) != reflect.TypeOf(&DESCipher64{}) { t.Fatalf("should be DESCipher64") } if reflect.TypeOf(f.GetCipher128()) != reflect.TypeOf(&DESCipher128{}) { t.Fatalf("should be DESCipher128") } } ``` ## 小結 抽象工廠模式也符合單一職責原則和開閉原則,不過需要引入大量的類和介面,使程式碼更加複雜。並且,當增加新的具體產品時,需要修改抽象工廠和所有的具體工廠。 # 總結 今天介紹了建立型模式之工廠模式,工廠模式包括簡單工廠、工廠方法和抽象工廠。簡單工廠的複雜性比較低,但是不像工廠方法和抽象工廠符合單一職責原則和開閉原則。實際使用時,通常會選擇符合開閉原則,複雜度也不是特別高的工廠方法。如果有特別需求可以選擇使用抽象工廠。