[Golang修仙之路] 工厂模式

97 阅读7分钟

1. 什么是工厂模式?

市面上的大多数教程都把工厂模式分为3个, 俗称“工厂三兄弟”. 小徐先生的文章中还介绍了「容器工厂」,我这里不打算介绍. 我觉得面试能把「简单工厂模式」+「工厂方法模式」+「抽象工厂模式」说清楚, 就足够了.

说回来, 什么是工厂模式呢? 就是创建类的实例的时候, 不通过类的定义直接创建, 而是从工厂中获取, 这样就可以屏蔽一些创建类时的细节, 还可以起到类的定义和类的使用解耦合的效果.

如果你想看一些英文资源, 搜索 factory pattern 或者 factory method

2. 应用场景

  1. 类的实例创建逻辑复杂的场景: 比如创建数据库连接池. 工厂方法模式可以屏蔽掉底层细节, 实现复用.
  2. 你需要在创建时统一做点儿什么的场景: 工厂方法提供了一个统一的切面, 可以做一些统一的判断校验之类.
  3. 你需要创建不同类的实例的时候: 通过向工厂传参数, 来获取不同类的实例, 这时候适合用工厂模式.

3. 优缺点

  1. 屏蔽复杂的创建逻辑: 我认为这是工厂模式的最大优点.
  2. 提供一个天然的切面: 这是小徐先生文章里提到的. 你可以在这个切面中做一些创建实例之外的事情, 实现代码的复用.
  3. 解耦类的创建与使用, 良好的扩展性: 没有什么是加一层不能解决的, 如果有, 就再加一层. 你可以在工厂模式中充分体会这一点.

4. 实现

好了, 终于来看到实现了.

4.1 简单工厂模式

4.1.1 简单工厂类图

image.png

网上拿的图, 说个要点:

  • 工厂的创建产品的方法, 返回值是接口.

4.1.2 简单工厂代码实现

创建了三个球员实例, 分别是艾佛森、欧文、哈达威, 实现了球员接口. 球员工厂的创建方法依据传入的参数, 选择创建什么实例, 这形成了一个天然的切面, 代码在这里收了一个口子.

但是这样做的缺点是,不符合开闭原则(对扩展开放, 对修改封闭), 当我们需要新增一个球员「乔丹」时, 不得不修改工厂创建方法的大switch case, 这不够优雅. 咋办? 一层不够, 就再加一层, 于是我们有了 「工厂方法模式」

package main

import "fmt"

type Player interface {
	CrossOver()
}

type Hadawei struct {
	name string
}

func (h *Hadawei) CrossOver() {
	fmt.Println(h.name + "胯下体前变相")
}

type Aifosen struct {
	name string
}

func (a *Aifosen) CrossOver() {
	fmt.Println(a.name + "大幅度变相")
}

type Ouwen struct {
	name string
}

func (o *Ouwen) CrossOver() {
	fmt.Println(o.name + "胯下转身变相")
}

type PlayerFactory struct{}

func (pf *PlayerFactory) CreatePlayer(input string) Player {

    // 这个地方其实就形成了切面, 代码在这里集中, 收住了口子
    // 这里可以做一些其他的事情, 比如权限校验, 日志记录, 缓存等等
    // 这个例子纯图一乐
    namestr := fmt.Sprintf("小猪乔治学习了: %s", input)

    var p Player
    switch input {
    case "aifosen":
        p = &Aifosen{
            name: namestr,
        }
    case "hadawei":
        p = &Hadawei{
            name: namestr,
        }
    case "ouwen":
        p = &Ouwen{
            name: namestr,
        }
    default:
    }
    return p
}

func main() {
    pf := &PlayerFactory{}
    ai := pf.CreatePlayer("aifosen")
    ai.CrossOver()

    ow := pf.CreatePlayer("ouwen")
    ow.CrossOver()

    hd := pf.CreatePlayer("hadawei")
    hd.CrossOver()
}

4.2 工厂方法模式

4.2.1 类图

把工厂也抽象成接口(再加一层的思想), 这样的好处是, 需要添加新的产品时(这里是球员), 扩展性更好, 不需要修改已有的代码, 只需要创建一个新的工厂实现工厂接口即可.

但是, 可扩展性也带来了一些弊端, 比如:

  • 简单工厂的天然切面不复存在, 代码没有了统一的收口.
  • 第二点是, 每添加一种新的产品, 就要新增产品类 + 产品工厂类, 会导致类过多, 代码复杂性增强.

因为工厂方法符合开闭原则, 所以工厂方法被Gof收录到23种设计模式, 而简单工厂没有.

image.png

4.2.1 工厂方法代码实现

package main

import "fmt"

type Player interface {
	CrossOver()
}

type Hadawei struct {
	name string
}

func (h *Hadawei) CrossOver() {
	fmt.Println(h.name + "胯下体前变相")
}

type Aifosen struct {
	name string
}

func (a *Aifosen) CrossOver() {
	fmt.Println(a.name + "大幅度变相")
}

type Ouwen struct {
	name string
}

func (o *Ouwen) CrossOver() {
	fmt.Println(o.name + "胯下转身变相")
}

type PlayerFactory interface{
	CreatePlayer(input string) Player
}

type OuwenFactory struct{}

func (of *OuwenFactory) CreatePlayer(input string) Player {
	return &Ouwen{
		name: input,
	}
}

type AifosenFactory struct{}

func (af *AifosenFactory) CreatePlayer(input string) Player {
	return &Aifosen{
		name: input,
	}
}

type HadaweiFactory struct{}

func (hf *HadaweiFactory) CreatePlayer(input string) Player {
	return &Hadawei{
		name: input,
	}
}

func main() {
	hf := &HadaweiFactory{}
	af := &AifosenFactory{}
	of := &OuwenFactory{}

	p1 := hf.CreatePlayer("hadewei 1号")
	p2 := af.CreatePlayer("aifosen 1号")
	p3 := of.CreatePlayer("ouwen 1号")
	p1.CrossOver()
	p2.CrossOver()
	p3.CrossOver()
}

4.3 抽象工厂模式

首先要搞清楚,产品族 和 产品等级结构 的概念。产品等级结构是相对稳定的, 也就是想要获得 简单工厂模式的优点, 因为如果不需要创建新的产品(不需要扩展), 简单工厂模式基本全是优点。 同时也要获得 工厂方法模式的优点, 这就来了 产品族,一个产品族,会对应创建一个工厂实例,去实现工厂接口。

产品族可以扩展, 而产品等级结构不会变化。

4.3.1 类图

这个场景是纯原创的:一个篮球队5个位置,对应产品等级结构。不同国家的球队,对应产品族。

image.png

4.3.2 抽象工厂模式代码

我觉得篮球队这个场景挺合适的。一个球队就五个位置:控球后卫、得分后卫、中锋、小前锋、大前锋。这对应产品等级结构。而不同的球队,比如:丹佛掘金、休斯顿火箭、洛杉矶快船等等,对应产品族。产品族可以扩展,我有钱我可以在天津大港油田搞一个:大港油田蛤蜊队。但是产品等级结构不会变化,再有钱,短时间内也很难在篮球这项运动中增加一个位置:“后锋” ? hhh

定义产品等级结构的接口

type PF interface {
	PFShoot()
}

type SF interface {
	SFShoot()
}

type PG interface {
	PGShoot()
}

type SG interface {
	SGShoot()
}

type C interface {
	CShoot()
}

定义2个产品族:中国队的这些球员和美国队的这些球员。

type ChinesePF struct {
	name string
}
func (c *ChinesePF) PFShoot() {
	println(c.name + " is shooting a three-pointer!")
}

type ChineseSF struct {
	name string
}
func (c *ChineseSF) SFShoot() {
	println(c.name + " is shooting a dunk!")
}
type ChinesePG struct {
	name string
}
func (c *ChinesePG) PGShoot() {
	println(c.name + " is shooting a layup!")
}
type ChineseSG struct {
	name string
}
func (c *ChineseSG) SGShoot() {
	println(c.name + " is shooting a free throw!")
}
type ChineseC struct {
	name string
}
func (c *ChineseC) CShoot() {
	println(c.name + " is shooting a hook shot!")
}
type AmericanPF struct {
	name string
}
func (a *AmericanPF) PFShoot() {
	println(a.name + " is shooting a three-pointer!")
}
type AmericanSF struct {
	name string
}
func (a *AmericanSF) SFShoot() {
	println(a.name + " is shooting a dunk!")
}
type AmericanPG struct {
	name string
}
func (a *AmericanPG) PGShoot() {
	println(a.name + " is shooting a layup!")
}
type AmericanSG struct {
	name string
}
func (a *AmericanSG) SGShoot() {
	println(a.name + " is shooting a free throw!")
}
type AmericanC struct {
	name string
}
func (a *AmericanC) CShoot() {
	println(a.name + " is shooting a hook shot!")
}

定义球员工厂接口,并让中国球员工厂和美国球员工厂分别实现它。

type PlayerFactory interface {
	CreatePF(name string) PF
	CreateSF(name string) SF
	CreatePG(name string) PG
	CreateSG(name string) SG
	CreateC(name string) C
}

type ChinesePlayerFactory struct{}
func (f *ChinesePlayerFactory) CreatePF(name string) PF {
	return &ChinesePF{name: name}
}
func (f *ChinesePlayerFactory) CreateSF(name string) SF {
	return &ChineseSF{name: name}
}
func (f *ChinesePlayerFactory) CreatePG(name string) PG {
	return &ChinesePG{name: name}
}
func (f *ChinesePlayerFactory) CreateSG(name string) SG {
	return &ChineseSG{name: name}
}
func (f *ChinesePlayerFactory) CreateC(name string) C {
	return &ChineseC{name: name}
}

type AmericanPlayerFactory struct{}
func (f *AmericanPlayerFactory) CreatePF(name string) PF {	
	return &AmericanPF{name: name}
}
func (f *AmericanPlayerFactory) CreateSF(name string) SF {
	return &AmericanSF{name: name}
}
func (f *AmericanPlayerFactory) CreatePG(name string) PG {
	return &AmericanPG{name: name}
}
func (f *AmericanPlayerFactory) CreateSG(name string) SG {
	return &AmericanSG{name: name}
}
func (f *AmericanPlayerFactory) CreateC(name string) C {
	return &AmericanC{name: name}
}

测试代码

func main() {
	cf := &ChinesePlayerFactory{}
	pf := cf.CreatePF("Wang ZhiZhi")
	pf.PFShoot()

	sf := cf.CreateSF("Yi JianLian")
	sf.SFShoot()

	af := &AmericanPlayerFactory{}
	pf2 := af.CreatePF("Kevin Durant")
	pf2.PFShoot()
	sf2 := af.CreateSF("LeBron James")
	sf2.SFShoot()
}
// output:
// Wang ZhiZhi is shooting a three-pointer!
// Yi JianLian is shooting a dunk!
// Kevin Durant is shooting a three-pointer!
// LeBron James is shooting a dunk!

如果我们想要再搞一个大港油田蛤蜊队,该咋办呢?首先我们要创建5个类,实现对应的球员位置接口。然后我们要创建一个「大港油田蛤蜊队」工厂,实现「球员工厂」接口。然后呢?然后就OK了。可以看到开闭原则就是很爽,不需要动任何的现有代码。

但是,如果我们此时需要修改产品等级结构,将会非常难受,因为每一个对应的产品族的实例,都需要做出变更。

5. 参考

  1. 小徐先生的编程世界: mp.weixin.qq.com/s/Fp0KxoXc4…
  2. 《深入设计模式》: refactoringguru.cn/design-patt…
  3. StackOverFlow: stackoverflow.com/questions/6…
  4. 刘丹冰,Easy设计模式:www.bilibili.com/video/BV1we…