这次介绍的设计模式是工厂模式,这是一个比较常见的创建型模式。一般情况下,工厂模式分为三种:简单工厂、工厂方法和抽象工厂,下面慢慢举例介绍下。

简单工厂

考虑一个加密程序的应用场景,一个加密程序可能提供了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]设计模式-工厂模式的更多相关文章

  1. .NET设计模式: 工厂模式

    .NET设计模式: 工厂模式(转) 转自:http://www.cnblogs.com/bit-sand/archive/2008/01/25/1053207.html   .NET设计模式(1): ...

  2. 【设计模式】Java设计模式 -工厂模式

    [设计模式]Java设计模式 -工厂模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目 ...

  3. [03]Go设计模式:工厂模式(Factory Pattern)

    目录 工厂模式 一.简介 二.代码 三.参考资料 工厂模式 一.简介 工厂模式(Factory Pattern)是软件设计中最常用的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象 ...

  4. 设计模式-工厂模式(Factory Pattern)

    本文由@呆代待殆原创,转载请注明出处. 工厂模式遵循的设计原则之一:找出代码中常变化的部分,并把这一部分分离出来.(Dependency Inversion Principle) 工厂模式简述 当我们 ...

  5. 设计模式 - 工厂模式(factory pattern) 具体解释

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/27081511 工厂模式(factory pa ...

  6. 23种设计模式--工厂模式-Factory Pattern

    一.工厂模式的介绍       工厂模式让我们相到的就是工厂,那么生活中的工厂是生产产品的,在代码中的工厂是生产实例的,在直白一点就是生产实例的类,代码中我们常用new关键字,那么这个new出来的实例 ...

  7. [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  8. javascript 设计模式-----工厂模式

    所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...

  9. C#设计模式-工厂模式

    引入人.工厂.和斧子的问题 原始社会时,劳动社会基本没有分工,需要斧子的人(调用者)只好自己去磨一把斧子,每个人拥有自己的斧子,如果把大家的石斧改为铁斧,需要每个人都要学会磨铁斧的本领,工作效率极低. ...

随机推荐

  1. Nginx环境下,PHP下载,中文文件,下载失效(英文可以下载)怎么解决呢?

    参考出处: http://www.imooc.com/qadetail/76393 Nginx环境下,PHP下载,中文文件,下载失效(英文可以下载)怎么解决呢? 背景介绍: 文件名  为英文时可以下载 ...

  2. CSS pseudo classes All In One

    CSS pseudo classes All In One CSS 伪类 https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes ...

  3. js animation & requestAnimationFrame

    js animation & requestAnimationFrame https://developer.mozilla.org/en-US/docs/Web/API/window/req ...

  4. HTML5 image rotate effect

    HTML5 image rotate effect HTML5 实现旋转拨号键盘 简单的html中js实现图片中心旋转 https://download.csdn.net/download/q3168 ...

  5. web & js & touch & gesture

    web & js & touch & gesture 触摸 & 手势 https://caniuse.com/#feat=touch js https://develo ...

  6. Dart: 解析html字符串

    安装html包 import 'package:http/http.dart' as http; import 'package:html/parser.dart' show parse; impor ...

  7. NGK的去中心化自治实践,更人性化的DAO

    2020年,DeFi市场市场火爆的同时,引爆了流动性挖矿的市场.行业内对DAO的思考也在源源不断进行,特别项目治理通证发行之前,DAO的去中心化的治理理念,是区块链属性中的重要的一环,也已引发了不同项 ...

  8. 使用docker mediawiki,搭建网页wiki

    我只是想做一个大家都能访问的wiki,用于成员间共享和维护一些文档.找到了docker的mediawiki,这里记录一下我怎么搭的吧. 首先,如果你在一个局域网里,有公用的可以访问的服务器,那可以直接 ...

  9. Vuex入门、同步异步存取值进阶版

    关注公众号查看原文: 1. vueX介&绍安装 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方 ...

  10. Python和JavaScript在使用上有什么区别?

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://www.freecodecamp.org/news/python-vs-javas ...