[Design Pattern With Go]設計模式-工廠模式
阿新 • • 發佈:2021-03-29
這次介紹的設計模式是工廠模式,這是一個比較常見的建立型模式。一般情況下,工廠模式分為三種:簡單工廠、工廠方法和抽象工廠,下面慢慢舉例介紹下。
# 簡單工廠
考慮一個加密程式的應用場景,一個加密程式可能提供了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")
}
}
```
## 小結
抽象工廠模式也符合單一職責原則和開閉原則,不過需要引入大量的類和介面,使程式碼更加複雜。並且,當增加新的具體產品時,需要修改抽象工廠和所有的具體工廠。
# 總結
今天介紹了建立型模式之工廠模式,工廠模式包括簡單工廠、工廠方法和抽象工廠。簡單工廠的複雜性比較低,但是不像工廠方法和抽象工廠符合單一職責原則和開閉原則。實際使用時,通常會選擇符合開閉原則,複雜度也不是特別高的工廠方法。如果有特別需求可以選擇使用抽象工廠。