Go语言设计模式(3)抽象工厂模式

Go语言设计模式(3)抽象工厂模式

抽象工厂模式的定义

抽象工厂模式的定义如下:

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

为创建一组相关或者相互依赖的对象提供一个接口,而且无须指定它们的具体类。

——《设计模式之禅》

好吧,就跟它的名字一样,它的定义也很抽象,很让人摸不着头脑。但其实这种设计模式还是很好理解的,只要明白了它和工厂模式有什么区别,再看一些实例代码,理解这种设计模式并不是难事。我们接下来就来具体讲讲它。

抽象工厂模式和工厂模式的区别

首先考虑一个问题:抽象工厂模式和工厂模式的区别在哪?

抽象工厂模式最大的特征就是定义中提到的“相关或相互依赖的对象”,通俗点说,就是抽象工厂模式相比工厂模式多了两个概念:“产品族”和“等级结构”。工厂模式中的每个工厂只能生产一种产品,而抽象工厂模式将某些相关、等级结构不同的产品组成了一个“产品族”,然后用一个工厂来统一生产。

还是觉得很难理解?好的,Show you code!

抽象工厂模式的简单例子

这里我没有使用《设计模式之禅》的例子,因为它比较复杂,我们来举个更简单更好理解的例子:

我们可以把Web应用简单地看做是由前端(客户端)和后端(服务端)组成的,不知道在看我的文章的同学中有没有知道“前端架构师”这个职业的同学,我们一直以为做后端开发更容易成为架构师,但是当前端变得越来越复杂时,前端架构师这种职位也就产生了。所以,在这个例子中,我们把架构师分为“前端架构师”和“后端架构师”两类。当然,要开发一个完整的Web应用,光有架构师还不行,还得有程序员来把架构师给出的设计用代码实现,而我们都知道程序员有做前端开发的,也有做后端开发的,这样也可以把程序员分为“前端程序员”和“后端程序员”两类。

在这个场景中,我们有四种产品:前端程序员、前端架构师、后端程序员、后端架构师。我们可以把“前端”和“后端”分为两个不同的等级结构,我们可以为这两个等级结构创建两个工厂类FrontEndFactoryBackEndFactory,我们同时也可以把“程序员”和“架构师”分为另外两个不同的产品族,在每个工厂类里添加CreateProgrammer()CreateArchitect()两个方法用于创建程序员和架构师的实例——也就是说,有多少个产品族,在抽象工厂类里就有多少个创建方法

根据这个场景我们可以画出下面这张UML类图(稍微有点不太标准,不过能通过这张图把上面描述的场景搞懂就OK了):

image

接下来用代码实现它:

abstract_factory/programmer_factory.go

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="go" cid="n39" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">package abstract_factory

import "fmt"

// Programmer 程序员总称
type Programmer interface {
Work() // 程序员都会工作
}

// Architect 架构师总称
type Architect interface {
Design() // 架构师都会做架构设计
}

// FrontEndArchitect 前端架构师
type FrontEndArchitect struct{}

func (a *FrontEndArchitect) Design() {
fmt.Println("前端架构师做了一个页面秒开的设计")
}

// FrontEndProgrammer 前端程序员
type FrontEndProgrammer struct{}

func (p *FrontEndProgrammer) Work() {
fmt.Println("前端程序员在用WebStorm写TypeScript代码")
}

// BackEndArchitect 后端架构师
type BackEndArchitect struct{}

func (a *BackEndArchitect) Design() {
fmt.Println("后端架构师做了一个可以抗住上万并发的设计")
}

// BackEndProgrammer 后端程序员
type BackEndProgrammer struct{}

func (p *BackEndProgrammer) Work() {
fmt.Println("后端程序员在用GoLand写Golang代码")
}

// AbstractFactory 抽象工厂
type AbstractFactory interface {
CreateProgrammer() Programmer // 创建程序员
CreateArchitect() Architect // 创建架构师
}

// FrontEndFactory 前端工厂
type FrontEndFactory struct{}

func (f *FrontEndFactory) CreateProgrammer() Programmer {
return &FrontEndProgrammer{}
}

func (f *FrontEndFactory) CreateArchitect() Architect {
return &FrontEndArchitect{}
}

// BackEndFactory 后端工厂
type BackEndFactory struct{}

func (f *BackEndFactory) CreateProgrammer() Programmer {
return &BackEndProgrammer{}
}

func (f *BackEndFactory) CreateArchitect() Architect {
return &BackEndArchitect{}
}
</pre>

代码比较长,可以对照着类图来看。

写个场景类(测试)来调用一下:

abstract_factory/programmer_factory_test.go

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="go" cid="n48" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">package abstract_factory

import (
"fmt"
"testing"
)

func TestCreateProgrammerAndArchitect(t *testing.T) {
fmt.Println("前端组招到了一个程序员和一个架构师")
ff := FrontEndFactory{}
fa := ff.CreateArchitect()
fp := ff.CreateProgrammer()
fmt.Println("前端组接到任务,开始工作...")
fa.Design()
fp.Work()
fmt.Println("后端组招到了一个程序员和一个架构师")
bf := BackEndFactory{}
ba := bf.CreateArchitect()
bp := bf.CreateProgrammer()
fmt.Println("后端组接到任务,开始工作...")
ba.Design()
bp.Work()
}
</pre>

运行结果:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n53" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">=== RUN TestCreateProgrammerAndArchitect
前端组招到了一个程序员和一个架构师
前端组接到任务,开始工作...
前端架构师做了一个页面秒开的设计
前端程序员在用WebStorm写TypeScript代码
后端组招到了一个程序员和一个架构师
后端组接到任务,开始工作...
后端架构师做了一个可以抗住上万并发的设计
后端程序员在用GoLand写Golang代码
--- PASS: TestCreateProgrammerAndArchitect (0.00s)
PASS</pre>

看完这个例子之后,是不是彻底明白抽象工厂模式是怎么一回事了呢。

我们也可以看出,在抽象工厂模式中,添加等级结构很方便——假如我们要添加一个新的等级结构“基础架构(BasicArchitecture)”,那么直接新建对应的程序员类BasicArchitectureProgrammer和架构师类BasicArchitectureArchitect继承ProgrammerArchitect,再新建一个工厂类BasicArchitectureFactory继承AbstractFactory就可以完成扩展。

但是,我们也很容易发现添加产品族很难,且违背了开闭原则——假如我们要添加一个新的工种“测试工程师(TestEngineer)”,则需要建立新的抽象类TestEngineer(成员方法可以叫Test()),并且在所有的工厂类里面都添加、实现CreateTestEngineer()用于创建不同等级结构(前端、后端)的测试工程师,这样才可以完成扩展。这种方法显而易见地违背了开闭原则,增大了维护难度,所以在使用抽象工厂模式时最好一开始就把所有的产品族设计好,尽量减少对产品族的添加和删除。

抽象工厂模式的通用代码

abstract_factory/abstract_factory.go

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="go" cid="n66" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">package abstract_factory

import "fmt"

// AbstractProductA 抽象产品类A
type AbstractProductA interface {
DoSomethingA()
}

// AbstractProductB 抽象产品类B
type AbstractProductB interface {
DoSomethingB()
}

// ProductA1 产品A1的实现类
type ProductA1 struct{}

func (p *ProductA1) DoSomethingA() {
fmt.Println("产品A1的实现方法")
}

// ProductA2 产品A2的实现类
type ProductA2 struct{}

func (p *ProductA2) DoSomethingA() {
fmt.Println("产品A2的实现方法")
}

// ProductB1 产品B1的实现类
type ProductB1 struct{}

func (p *ProductB1) DoSomethingB() {
fmt.Println("产品B1的实现方法")
}

// ProductB2 产品B2的实现类
type ProductB2 struct{}

func (p *ProductB2) DoSomethingB() {
fmt.Println("产品B2的实现方法")
}

// AbstractCreator 抽象工厂类
type AbstractCreator interface {
CreateProductA() AbstractProductA
CreateProductB() AbstractProductB
}

// Creator1 产品等级1的工厂实现类
type Creator1 struct{}

func (c *Creator1) CreateProductA() AbstractProductA {
return &ProductA1{}
}

func (c *Creator1) CreateProductB() AbstractProductB {
return &ProductB1{}
}

// Creator2 产品等级2的工厂实现类
type Creator2 struct{}

func (c *Creator2) CreateProductA() AbstractProductA {
return &ProductA2{}
}

func (c *Creator2) CreateProductB() AbstractProductB {
return &ProductB2{}
}
</pre>

abstract_factory/abstract_factory_test.go

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="go" cid="n71" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">package abstract_factory

import "testing"

func TestAbstractFactory(t *testing.T) {
c1 := Creator1{}
c2 := Creator2{}
a1 := c1.CreateProductA()
a2 := c2.CreateProductA()
b1 := c1.CreateProductB()
b2 := c2.CreateProductB()
a1.DoSomethingA()
a2.DoSomethingA()
b1.DoSomethingB()
b2.DoSomethingB()
}
</pre>

运行结果:

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="" cid="n76" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--font-monospace); font-size: 0.85rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248) !important; position: relative !important; width: inherit; border: 1px solid rgb(244, 244, 244); -webkit-font-smoothing: initial; line-height: 1.43rem; border-top-left-radius: 2px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; word-wrap: normal; margin: 0.8rem 0px !important; padding: 0.3rem 0px !important; caret-color: rgb(52, 73, 94); color: rgb(52, 73, 94); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; background-position: inherit; background-repeat: inherit;">=== RUN TestAbstractFactory
产品A1的实现方法
产品A2的实现方法
产品B1的实现方法
产品B2的实现方法
--- PASS: TestAbstractFactory (0.00s)
PASS</pre>

总结

抽象工厂模式看似很难理解,实际上只要理解了“产品族”和“等级结构”这两个概念,抽象工厂模式就非常简单了。那么它有什么用呢?一个很典型的用途就是适配不同数据库——不同的数据库所提供的操作应该是相同的(增、删、改、查、事务等),但是每种数据库的底层实现又不一样,这时就可以使用抽象工厂模式来生成不同数据库的操作对象;另外,Java的AWT也运用了抽象工厂模式来实现不同操作系统下应用程序界面的统一。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容