这次介绍的设计模式是工厂模式,这是一个比较常见的创建型模式。一般情况下,工厂模式分为三种:简单工厂、工厂方法和抽象工厂,下面慢慢举例介绍下。
简单工厂
考虑一个加密程序的应用场景,一个加密程序可能提供了AES,DES等加密方法,这些加密方式都实现了同一个接口ICipher,它有两个方法分别是 Encript 和 Decript。我们使用加密程序的时候会希望简单的指定加密方式,然后传入原始数据以及必要参数,然后就能得到想要的加密数据。这个功能用简单工厂如何实现呢?
模式结构
简单工厂模式包含一下几个角色:
- Factory(工厂角色),负责创建所有实例。
- Product(抽象产品角色),指工厂所创建的实例的基类,在 golang 中通常为接口。
- ConcreteProduce(具体产品),指工厂所创建的具体实例的类型。
在这个加密程序的例子中,工厂角色的职责是返回加密函数;抽象产品角色是所有加密类的基类,在 golang 中是定义了加密类通用方法的接口;具体产品是指具体的加密类,如 AES、DES 等等。我们可以用 UML 关系图来表示这几个角色之间的关系:
代码设计
依据 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 图来表示这几个角色直接的关系:
代码设计
抽象产品角色和具体产品角色就不再定义了,和简单工厂相同,具体展示一下抽象工厂角色和具体工厂角色。
抽象工厂角色定义了一个方法,用于创建对应的产品:
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 关系图:
代码设计
抽象产品和具体产品的定义与工厂方法类似:
抽象产品:
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")
}
}
小结
抽象工厂模式也符合单一职责原则和开闭原则,不过需要引入大量的类和接口,使代码更加复杂。并且,当增加新的具体产品时,需要修改抽象工厂和所有的具体工厂。
总结
今天介绍了创建型模式之工厂模式,工厂模式包括简单工厂、工厂方法和抽象工厂。简单工厂的复杂性比较低,但是不像工厂方法和抽象工厂符合单一职责原则和开闭原则。实际使用时,通常会选择符合开闭原则,复杂度也不是特别高的工厂方法。如果有特别需求可以选择使用抽象工厂。