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

LooJee 發表於 2021-03-29

這次介紹的設計模式是工廠模式,這是一個比較常見的建立型模式。一般情況下,工廠模式分為三種:簡單工廠、工廠方法和抽象工廠,下面慢慢舉例介紹下。

簡單工廠

考慮一個加密程式的應用場景,一個加密程式可能提供了AES,DES等加密方法,這些加密方式都實現了同一個介面ICipher,它有兩個方法分別是 Encript 和 Decript。我們使用加密程式的時候會希望簡單的指定加密方式,然後傳入原始資料以及必要引數,然後就能得到想要的加密資料。這個功能用簡單工廠如何實現呢?

模式結構

簡單工廠模式包含一下幾個角色:

  • Factory(工廠角色),負責建立所有例項。
  • Product(抽象產品角色),指工廠所建立的例項的基類,在 golang 中通常為介面。
  • ConcreteProduce(具體產品),指工廠所建立的具體例項的型別。

在這個加密程式的例子中,工廠角色的職責是返回加密函式;抽象產品角色是所有加密類的基類,在 golang 中是定義了加密類通用方法的介面;具體產品是指具體的加密類,如 AES、DES 等等。我們可以用 UML 關係圖來表示這幾個角色之間的關係:

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

程式碼設計

依據 UML 關係圖,我們可以設計出採用簡單工廠模式的加密程式碼。首先是 ICipher 介面,定義了 Encript 和 Decript 兩個方法:

type ICipher interface {
	Encrypt([]byte) ([]byte, error)
	Decrypt([]byte) ([]byte, error)
}

然後根據這個介面,分別實現 AESCipher 和 DESCipher 兩個加密類。

AESCipher:

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:

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 需要實現一個工廠類,這裡我們用一個函式來做加密類工廠:

func CipherFactory(cType string) ICipher {
	switch cType {
	case "AES":
		return NewAESCipher()
	case "DES":
		return NewDesCipher()
	default:
		return nil
	}
}

這樣,通過呼叫 CipherFactory 傳入所需的加密型別,就可以得到所需要的加密類例項了。

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 圖來表示這幾個角色直接的關係:
[Design Pattern With Go]設計模式-工廠模式

程式碼設計

抽象產品角色和具體產品角色就不再定義了,和簡單工廠相同,具體展示一下抽象工廠角色和具體工廠角色。
抽象工廠角色定義了一個方法,用於建立對應的產品:

type ICipherFactory interface {
	GetCipher() ICipher
}

根據這個介面,分別定義出 AESCipherFactory、和 DESCipherFactory 兩個子類工廠。

AESCipherFactory

type AESCipherFactory struct {
}

func (AESCipherFactory) GetCipher() ICipher {
	return NewAESCipher()
}

func NewAESCipherFactory() *AESCipherFactory {
	return &AESCipherFactory{}
}

DESCipherFactory

type DESCipherFactory struct {
}

func (DESCipherFactory) GetCipher() ICipher {
	return NewDESCipher()
}

func NewDESCipherFactory() *DESCipherFactory {
	return &DESCipherFactory{}
}

然後編寫一個單元測試來檢驗我們的程式碼:

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 個具體產品。

AESCipher64
AESCipher128
AESCipherFactory64
AESCipherFactory128
DESCipher64
DESCipher128
DESCipherFactory64
DESCipherFactory128

這時候,我們可以把有關聯性的具體產品組合成一個產品組,例如AESCipher64 和 AESCipher128,讓它們通過同一個工廠 AESCipherFactory 來生產,這樣就可以簡化成 2 個具體工廠和 4 個具體產品

AESCipher64
AESCipher128
AESCipherFactory
DESCipher64
DESCipher128
DESCipherFactory

這就是抽象工廠模式。

模式結構

抽象工廠共有 4 個角色:

  • AbstractFactory(抽象工廠):定義工廠的介面。
  • ConcreteFactory(具體工廠):實現抽象工廠,生產具體產品。
  • AbstractProduct(抽象產品):定義產品的介面。
  • Product(具體產品):具體的產品例項。

根據角色定義我們可以畫出抽象工廠的 UML 關係圖:
[Design Pattern With Go]設計模式-工廠模式

程式碼設計

抽象產品和具體產品的定義與工廠方法類似:

抽象產品

type ICipher interface {
	Encrypt(data, key[]byte) ([]byte, error)
	Decrypt(data, key[]byte) ([]byte, error)
}

AESCipher64

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


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

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

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 兩個方法定義:

type ICipherFactory interface {
	GetCipher64() ICipher
	GetCipher128() ICipher
}

然後分別實現 AESCipherFactory 和 DesCipherFactory 兩個具體工廠:

AESCipherFactory

type AESCipherFactory struct {
}

func (AESCipherFactory) GetCipher64() ICipher {
	return NewAESCipher64()
}

func (AESCipherFactory) GetCipher128() ICipher {
	return NewAESCipher128()
}

func NewAESCipherFactory() *AESCipherFactory {
	return &AESCipherFactory{}
}

DESCipherFactory

type DESCipherFactory struct {
}

func (DESCipherFactory) GetCipher64() ICipher {
	return NewDESCipher64()
}

func (DESCipherFactory) GetCipher128() ICipher {
	return NewDESCipher128()
}

func NewDESCipherFactory() *DESCipherFactory {
	return &DESCipherFactory{}
}

編寫單元測試驗證我們的程式碼:

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")
	}
}

小結

抽象工廠模式也符合單一職責原則和開閉原則,不過需要引入大量的類和介面,使程式碼更加複雜。並且,當增加新的具體產品時,需要修改抽象工廠和所有的具體工廠。

總結

今天介紹了建立型模式之工廠模式,工廠模式包括簡單工廠、工廠方法和抽象工廠。簡單工廠的複雜性比較低,但是不像工廠方法和抽象工廠符合單一職責原則和開閉原則。實際使用時,通常會選擇符合開閉原則,複雜度也不是特別高的工廠方法。如果有特別需求可以選擇使用抽象工廠。