工厂方法模式(Factory Method)
包括两种实现方法:
- 1.简单工厂模式(Simple Factory)
- 2.工厂方法模式(Factory Method)
1.简单工厂模式
将对象的创建过程,用一个公共的函数封装起来,外部只需要调用该函数即可.很多go源码中都在使用这种方法.当看到NewXXX开头的函数,基本都是这种模式的体现.
啥也不说,上才艺
package simple
import (
"fmt"
)
type FoodKind int
const (
MeatKind FoodKind = iota
FruitKind
VegetableKind
)
//Food 食物接口
type Food interface {
Eat()
}
//肉-食物接口的实现
type meat struct {
}
func (t *meat) Eat() {
fmt.Println("Eat meat")
}
//水果-食物接口的实现
type fruit struct {
}
func (t *fruit) Eat() {
fmt.Println("Eat fruit")
}
//蔬菜-食物接口的实现
type vegetable struct {
}
func (t *vegetable) Eat() {
fmt.Println("Eat vegetable")
}
//NewFood 根据需要的类型,返回实现对象
func NewFood(k FoodKind) Food {
switch k {
case MeatKind:
return &meat{}
case FruitKind:
return &fruit{}
case VegetableKind:
return &vegetable{}
}
return nil
}
上面啰里啰嗦写了一堆代码.该模式的核心函数 传入一个参数,返回一个参数.外部import这个simple包的时候,只能通过NewFood获取到Food接口对象.
因为Food接口的实现结构体meat fruit vegetable都是英文首字母小写,外部是无法想这样simple.fruit{}使用到结构体,只能通过NewFood获取.
NewFood内部返回的是指针类型的对象,这个知道为什么吧?
因为返回的参数要求是Food接口的实现类型.我们在实现的时候,只实现了指针的类型.
如果我们想返回值类型应该怎么写呢?
//坚果-食物接口的实现
type nut struct {
}
func (t nut) Eat() { // !!!注意这里 t nut 而不是t *nut
fmt.Println("Eat nut")
}
//NewFood 根据需要的类型,返回实现对象
func NewFood(k FoodKind) Food {
switch k {
//....省略...
case NutKind:
return nut{} // &nut{} 两种写法都可以
}
return nil
}
这个就是Golang语言在interface实现上的规则了.记住即可:
指针类型的实现,只实现了指针类型
值类型的实现,即实现了指针类型又实现了值类型.
- 调用
func main() {
fmt.Println("简单工厂模式")
simple.NewFood(simple.MeatKind).Eat()
simple.NewFood(simple.FruitKind).Eat()
simple.NewFood(simple.VegetableKind).Eat()
simple.NewFood(simple.NutKind).Eat()
}
- 结果
简单工厂模式
Eat meat
Eat fruit
Eat vegetable
Eat nut
2.工厂方法模式
在简单工厂模式的基础上,再进一步的抽象.怎么抽象呢?"简单工厂模式"仅仅只是提供了对外的函数NewFood,如果我把这个函数再封装成interface对外提供能呢?
想想为什么?如果仅仅只有一个函数,所有对象的创建是不是只能在这个函数里面去追加?当对象少的时候,还好说,成百上千的对象,那光看这个一个函数,头都大了.如果能把不同的对象的创建放在类似的函数里面,是不是会清晰很多?
不理解,没关系,看代码,看完保证你理解
package method
import "fmt"
type FoodKind int
const (
MeatKind FoodKind = iota
FruitKind
VegetableKind
NutKind
)
//Food 食物接口
type Food interface {
Eat()
}
//肉-食物接口的实现
type meat struct {
}
func (t *meat) Eat() {
fmt.Println("Eat meat")
}
//水果-食物接口的实现
type fruit struct {
}
func (t *fruit) Eat() {
fmt.Println("Eat fruit")
}
//蔬菜-食物接口的实现
type vegetable struct {
}
func (t *vegetable) Eat() {
fmt.Println("Eat vegetable")
}
//坚果-食物接口的实现
type nut struct {
}
func (t nut) Eat() {
fmt.Println("Eat nut")
}
//-------------干饭人的分割线-----------
//食物工厂
type Factory interface {
NewFood(k FoodKind) Food //这个函数是不是和简单工厂模式一模一样,而且分割线上面的代码也是一模一样
}
//肉厂
type MeatFactory struct {
}
func (t MeatFactory) NewFood(k FoodKind) Food {
return &meat{}
}
//水果厂
type FruitFactory struct {
}
func (t FruitFactory) NewFood(k FoodKind) Food {
return &fruit{}
}
//蔬菜厂
type VegetableFactory struct {
}
func (t VegetableFactory) NewFood(k FoodKind) Food {
return &vegetable{}
}
//坚果厂
type NutFactory struct {
}
func (t NutFactory) NewFood(k FoodKind) Food {
return nut{}
}
这里FoodKind其实没有任何实际意义,只是为了让大家看的更直观些.已经是肉厂了,我还传入MeatKind其实没意义,应该传入更细分的肉,例如:鸡肉,鱼肉,猪肉等标识.
- 调用
func main() {
fmt.Println("工厂方法模式")
method.MeatFactory{}.NewFood(method.MeatKind).Eat() //去肉厂吃肉
method.FruitFactory{}.NewFood(method.FruitKind).Eat() //去水果厂吃水果
method.VegetableFactory{}.NewFood(method.VegetableKind).Eat()
method.NutFactory{}.NewFood(method.NutKind).Eat()
}
- 结果
工厂方法模式
Eat meat
Eat fruit
Eat vegetable
Eat nut
这样做的好处,我想吃肉,就找肉厂,想吃蔬菜,就去蔬菜场.类似于超市,会分不同的菜品区域一样,简单高效的即可找到所需的东西.
"简单工厂模式",更像是去赶集,没有固定的区域,大家卖肉,卖菜全部都混在一起,有可能饶了一大圈才能找到自己想要的东西.
工厂方法模式,其实就是单一职责原则和依赖倒置的体现;
单一职责原则:对于每个具体的实现结构体,我只负责一个具体的功能,不关我的事情,我不做.我只负责写bug,不是我写的我不改.
依赖倒置:具体的实现结构体的方法NewFood的返回参数,依赖于Food接口,而不是具体的某个实现,比如meat,扩展性会更友好.其实也是开放闭合原则的体现.
源码地址 :
https://github.com/gofish2020/gopattern/tree/main/creator
文章首发在微信公众号 "池鱼之交",有需要的可以关注下.
有疑问加站长微信联系(非本文作者)