工厂模式又可以分为工厂方法模式和抽象工厂模式。

工厂方法模式(Factory Method Pattern)

工厂模式是指:定义一个接口用户创建对象,让子类决定实例化哪一个类。

工厂模式中存在4个角色:

  1. 抽象工厂
  2. 具体工厂
  3. 抽象产品
  4. 具体产品

抽象工厂产生抽象产品,具体工厂生产具体产品。 这句话很重要,理解了这句话就理解了工厂方法模式。

看定义还是略微抽象,这里我们以客户购买汽水为例,初学编程的人很有可能出现类似下面的代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var require string
fmt.Printf("Please enter your choice(cola,spirte,fanta): ")
fmt.Scanln(&require)
if require == "cola" {
fmt.Println("this is cola")
} else if require == "sprite" {
fmt.Println("this is sprite")
} else if require == "fanta" {
fmt.Println("this is fanta")
} else {
panic("What you want?")
}
}

这段代码很好理解,用户输入他想要购买的汽水,然后程序返回他的选择,如果用户输入的不在上述3个选择内就报错What you want?。但基本上没有可维护性、扩展性,也不能方便的进行复用。为了实现高内聚低耦合的目标,我们做一些修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
)

type showname interface {
show()
}

type cola struct {}
func (e cola) show() {
fmt.Println("this is cola")
}

type sprite struct {}
func (e sprite) show() {
fmt.Println("this is sprite")
}

type fanta struct {}
func (e fanta) show() {
fmt.Println("this is fanta")
}

type factory struct{}
func (e factory) showname(name string) showname {
switch name {
case "cola":
return cola{}
case "sprite":
return sprite{}
case "fanta":
return fanta{}
default:
panic("What you want?")
}
}

func main() {
var require string
fmt.Printf("Please enter your choice(cola,spirte,fanta): ")
fmt.Scanln(&require)
fac := factory{}
fac.showname(require).show()
}

由于golang中没有类、继承相关的概念,所以这里使用struct和interface来实现。采用上面这种修改后,看上去更复杂了,但却提高了代码的复用率。如果以后有其他地方也要用到这个功能,直接调用factory即可,而不是把那堆if...else复制过去。这种写法还有个学名叫做 简单工厂模式 ,这个模式优点是工厂类中包含了逻辑判断,根据调用方传入的参数动态实例化相关的类,如果需要修改功能不需要修改调用方的代码。但问题也在这里,如果要新增一个类,那么是要修改case分支条件的,修改原有的类违反了开闭原则,这时候就该工厂方法模式出场了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
"fmt"
)

// 抽象工厂
type showFactory interface {
info() saleinfo
}

// 具体工厂
type showColaFactory struct{}

func (s showColaFactory) info() saleinfo {
return cola{soda:soda{name:"cola",price:5}}
}

// 具体工厂
type showSpriteFactory struct{}

func (s showSpriteFactory) info() saleinfo {
return cola{soda:soda{name:"sprite",price:5}}
}

// 具体工厂
type showFantaFactory struct{}

func (s showFantaFactory) info() saleinfo {
return fanta{soda:soda{name:"fanta",price:5}}
}

// 抽象产品
type saleinfo interface {
getname()
getprice()
}
type soda struct {
name string
price int
}

//具体产品
type cola struct {
soda
}
func (e cola) getname() {
fmt.Printf("this is %s", e.name)
}
func (e cola) getprice() {
fmt.Printf("price is %d", e.price)
}

//具体产品
type sprite struct {
soda
}
func (e sprite) getname() {
fmt.Printf("this is %s", e.name)
}
func (e sprite) getprice() {
fmt.Printf("price is %d", e.price)
}

//具体产品
type fanta struct {
soda
}
func (e fanta) getname() {
fmt.Printf("this is %s", e.name)
}
func (e fanta) getprice() {
fmt.Printf("price is %d", e.price)
}


func main() {
var require string
fmt.Printf("Please enter your choice(cola,spirte,fanta): ")
fmt.Scanln(&require)
var s saleinfo
switch require {
case "cola":
s = showColaFactory{}.info()
case "sprite":
s = showSpriteFactory{}.info()
case "fanta":
s = showFantaFactory{}.info()
default:
panic("what you want?")
}
s.getname()
s.getprice()
}

WoW,代码更复杂了,但这样修改后,如果以后需要新增一个新的juice,只需新增相关的具体工厂和具体产品即可,不用修改原来的工厂类,符合了开闭原则。不过,这种方法把判断逻辑又丢给了调用方,有没有什么办法更进一步呢?答案就是抽象工厂模式,这个下面再说。

工厂方法模式应用场景

  1. 当子类型有很多,以后需要不断添加不同子类实现时。
  2. 一个系统在框架设计阶段,还不知道将来需要实例化哪些具体子类时。
  3. 系统设计之初不需要具体对象的概念或没有具体对象的概念。

抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体实现类。

进一步说,抽象工厂模式中调用方使用抽象接口来创建一组相关产品,实现了与工厂类的解耦。我们将卖汽水的例子复杂化一点,可以在汽水中加冰,那么用抽象工厂模式写法就变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import "fmt"

// 抽象汽水基类
type soda struct {
name string
price int
}

type sodaInfo interface {
buy()
}

// 抽象可乐基类,继承soda并实现sodaInfo接口
type cola struct {
soda
}
func (e cola) buy() {
fmt.Printf("this is %s \n", e.name)
fmt.Printf("price is %d \n", e.price)
}
// 具体可乐类,加冰
type colaIce struct {
cola
}

// 抽象雪碧基类,继承soda并实现sodaInfo接口
type sprite struct {
soda
}
func (e sprite) buy() {
fmt.Printf("this is %s \n", e.name)
fmt.Printf("price is %d \n", e.price)
}
// 具体雪碧类,加冰
type spriteIce struct {
sprite
}

// 抽象芬达基类,继承soda并实现sodaInfo接口
type fanta struct {
soda
}
func (e fanta) buy() {
fmt.Printf("this is %s \n", e.name)
fmt.Printf("price is %d \n", e.price)
}
// 具体芬达类,加冰
type fantaIce struct {
fanta
}

// 抽象汽水工厂,注意里面都是生产抽象汽水
type sodaAbsFactory interface {
createCola() sodaInfo
createSprite() sodaInfo
createFanta() sodaInfo
}

// 具体汽水工厂,生产具体汽水,实现抽象汽水工厂接口
type sodaFactory struct {}
func (s sodaFactory) createCola() sodaInfo {
return colaIce{
cola:cola{
soda:soda{name:"cola_with_ice",price: 10},
},
}
}
func (s sodaFactory) createSprite() sodaInfo {
return colaIce{
cola:cola{
soda:soda{name:"sprice_with_ice",price: 12},
},
}
}
func (s sodaFactory) createFanta() sodaInfo {
return colaIce{
cola:cola{
soda:soda{name:"fanta_with_ice",price: 8},
},
}
}

// 消费者类,需要买汽水时候向汽水工厂请求
type customer struct {
sodafac sodaAbsFactory
}
func (c customer) Buycola() {
c.sodafac.createCola().buy()
}
func (c customer) Buysprite() {
c.sodafac.createSprite().buy()
}
func (c customer) Buyfanta() {
c.sodafac.createFanta().buy()
}

func main() {
c := customer{sodafac: sodaFactory{}} // 创建消费者,并把汽水工厂传递进去
var require string
fmt.Printf("Please enter your choice(cola,spirte,fanta): ")
fmt.Scanln(&require)
switch require {
case "cola":
c.buyCola()
case "sprite":
c.buySprite()
case "fanta":
c.buyFanta()
default:
panic("what you want?")
}
}

似乎更更更复杂了,但假设突然需要更换另一家卖汽水的,调用方仅仅修改创建消费者那行代码就可以了。(这里别陷入误区:改1行代码和改100行代码可不是一回事,减少修改!=不修改。)至于判断逻辑的问题,可以使用反射来解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
var require string
fmt.Printf("Please enter your choice(cola,spirte,fanta): ")
fmt.Scanln(&require)
methodName := fmt.Sprintf("Buy%s",require)
c := customer{sodafac: sodaFactory{}} // 创建消费者,并把汽水工厂传递进去
getValue := reflect.ValueOf(c)
methodValue := getValue.MethodByName(methodName) // 注意所有的函数名都要大写开头,否则reflect找不到对应的函数会报错!
if methodValue.String() != "<invalid Value>" {
args := make([]reflect.Value, 0)
methodValue.Call(args)
}else {
panic("what you want?")
}
}

千万注意使用反射机制来调用函数时候的名称问题,非大写字母开头的函数MethodbyName是找不到的。虽然使用反射可以解决判断逻辑的问题,但是要不要在项目中这么使用则是见仁见智了。另外补充一句,这个反射行为在python中使用使用getattr()即可实现,或许这也是使用python时候并没怎么想到设计模式的原因之一吧。

抽象工厂模式应用场景

  1. 创建产品家族,相关产品集合在一起使用的时候。
  2. 提供一个产品类库,并只想显示其接口而不是实现的时候。
  3. 通过组合的方使使用工厂时。

对比

  1. 工厂方法模式通过继承的方式来解耦,抽象工厂模式则通过组合的方式实现解耦。
  2. 工厂方法模式用来创建一个抽象产品,具体工厂实现工厂方法来创建具体产品,抽象工厂模式用组合来创建一个产品家族的抽象类型。