[Design Pattern With Go]设计模式-工厂模式
这次介绍的设计模式是工厂模式,这是一个比较常见的创建型模式。一般情况下,工厂模式分为三种:简单工厂、工厂方法和抽象工厂,下面慢慢举例介绍下。
简单工厂
考虑一个加密程序的应用场景,一个加密程序可能提供了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")
	}
}
小结
抽象工厂模式也符合单一职责原则和开闭原则,不过需要引入大量的类和接口,使代码更加复杂。并且,当增加新的具体产品时,需要修改抽象工厂和所有的具体工厂。
总结
今天介绍了创建型模式之工厂模式,工厂模式包括简单工厂、工厂方法和抽象工厂。简单工厂的复杂性比较低,但是不像工厂方法和抽象工厂符合单一职责原则和开闭原则。实际使用时,通常会选择符合开闭原则,复杂度也不是特别高的工厂方法。如果有特别需求可以选择使用抽象工厂。
[Design Pattern With Go]设计模式-工厂模式的更多相关文章
- .NET设计模式: 工厂模式
		
.NET设计模式: 工厂模式(转) 转自:http://www.cnblogs.com/bit-sand/archive/2008/01/25/1053207.html .NET设计模式(1): ...
 - 【设计模式】Java设计模式 -工厂模式
		
[设计模式]Java设计模式 -工厂模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目 ...
 - [03]Go设计模式:工厂模式(Factory Pattern)
		
目录 工厂模式 一.简介 二.代码 三.参考资料 工厂模式 一.简介 工厂模式(Factory Pattern)是软件设计中最常用的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象 ...
 - 设计模式-工厂模式(Factory Pattern)
		
本文由@呆代待殆原创,转载请注明出处. 工厂模式遵循的设计原则之一:找出代码中常变化的部分,并把这一部分分离出来.(Dependency Inversion Principle) 工厂模式简述 当我们 ...
 - 设计模式 - 工厂模式(factory pattern) 具体解释
		
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/27081511 工厂模式(factory pa ...
 - 23种设计模式--工厂模式-Factory Pattern
		
一.工厂模式的介绍 工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...
 - [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式
		
系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...
 - javascript 设计模式-----工厂模式
		
所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...
 - C#设计模式-工厂模式
		
引入人.工厂.和斧子的问题 原始社会时,劳动社会基本没有分工,需要斧子的人(调用者)只好自己去磨一把斧子,每个人拥有自己的斧子,如果把大家的石斧改为铁斧,需要每个人都要学会磨铁斧的本领,工作效率极低. ...
 
随机推荐
- 正则表达式 test 踩坑指南
			
正则表达式 test 踩坑指南 test 只能使用一次,第二次返回的是错误结果! reg = /edg|edge/g; /edg|edge/g reg.test(`edg`) true reg.tes ...
 - Redis in Action
			
Redis in Action Redis REmote DIctionary Server(Redis) Redis 是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理. ...
 - Apache HTTP Server & WS (websockets)
			
Apache HTTP Server & WS (websockets) Apache HTTP Server Version 2.4 https://httpd.apache.org/doc ...
 - Puppeteer: 虚拟键盘
			
文档 main.js const pptr = require('puppeteer'); const gotoUrl = 'http://127.0.0.1:5500/index.html'; (a ...
 - “Fatal error: Unable to find local grunt.” when running “grunt” command
			
下载到本地 >npm install grunt >grunt 命令行运行:grunt,出现以下问题: 这些是Gruntfile.js中引用的,依次安装: npm install grun ...
 - ES进行date_histogram时间聚合,聚合结果时间不正确问题
			
在做项目中,有一个需求是统计本周内每天的漏洞数量,我选用的是ES中的date_histogram函数来进行聚合统计: 但是出现了一个问题,聚合出来的结果和想要统计的结果时间不一致,如下图所示 时间区间 ...
 - 2021-2-20:请你说说分布式系统 BASE 理论是什么?
			
BASE 理论是由 Dan Pritchett 在 ACM 上发表的一篇论文中提出的理论.是在 CAP 理论基础上提出的一种更实际的理论指导,和 PACELC 理论是有些相近的地方的. BASE 是指 ...
 - 百度 Apollo无人车平台增加传感器
			
https://github.com/ApolloAuto/apollo/issues/1649 如果想加入一个新的传感器不是百度官方推荐的传感器到Apollo平台做法: First you can ...
 - 16_MySQL聚合函数的使用(重点,建议大家多动手操作)
			
本节所涉及的SQL语句 -- 聚合函数 SELECT AVG(sal+IFNULL(comm,0)) AS avg FROM t_emp; -- SUM SELECT SUM(sal) FROM t_ ...
 - 聊聊CPU的LOCK指令
			
本文转载自聊聊CPU的LOCK指令 导语 在多线程操作中,可能最经常被提起的就是数据的可见性.原子性.有序性.不管是硬件方面.软件方面都在这三方面做了很足的工作,才能保证程序的正常运行. 之前发表过一 ...