1. 什么是工厂模式?
市面上的大多数教程都把工厂模式分为3个, 俗称“工厂三兄弟”. 小徐先生的文章中还介绍了「容器工厂」,我这里不打算介绍. 我觉得面试能把「简单工厂模式」+「工厂方法模式」+「抽象工厂模式」说清楚, 就足够了.
说回来, 什么是工厂模式呢? 就是创建类的实例的时候, 不通过类的定义直接创建, 而是从工厂中获取, 这样就可以屏蔽一些创建类时的细节, 还可以起到类的定义和类的使用解耦合的效果.
如果你想看一些英文资源, 搜索 factory pattern 或者 factory method
2. 应用场景
- 类的实例创建逻辑复杂的场景: 比如创建数据库连接池. 工厂方法模式可以屏蔽掉底层细节, 实现复用.
- 你需要在创建时统一做点儿什么的场景: 工厂方法提供了一个统一的切面, 可以做一些统一的判断校验之类.
- 你需要创建不同类的实例的时候: 通过向工厂传参数, 来获取不同类的实例, 这时候适合用工厂模式.
3. 优缺点
- 屏蔽复杂的创建逻辑: 我认为这是工厂模式的最大优点.
- 提供一个天然的切面: 这是小徐先生文章里提到的. 你可以在这个切面中做一些创建实例之外的事情, 实现代码的复用.
- 解耦类的创建与使用, 良好的扩展性: 没有什么是加一层不能解决的, 如果有, 就再加一层. 你可以在工厂模式中充分体会这一点.
4. 实现
好了, 终于来看到实现了.
4.1 简单工厂模式
4.1.1 简单工厂类图
网上拿的图, 说个要点:
- 工厂的创建产品的方法, 返回值是接口.
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种设计模式, 而简单工厂没有.
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个位置,对应产品等级结构。不同国家的球队,对应产品族。
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. 参考
- 小徐先生的编程世界: mp.weixin.qq.com/s/Fp0KxoXc4…
- 《深入设计模式》: refactoringguru.cn/design-patt…
- StackOverFlow: stackoverflow.com/questions/6…
- 刘丹冰,Easy设计模式:www.bilibili.com/video/BV1we…