我学设计模式-工厂模式
说起工厂模式呀,就想起当时看Spring源码的心情,难受至极,还不是因为优秀的框架总是伴随着那么多的设计模式。
学生时代上课也没有见过要学习设计模式这门课呀,再说鄙人也是非科班出身,更是没学过设计模式,所以当时看源码,一把辛酸泪。
属实没有办法,只好补好基本功,这不,毕业了,有时间可以重头学习一下设计模式,嗯... 就好像学生时代有时间就会去学习设计模式...
话收回来,我们认真开始了哈,进入主题。
先说说因为什么,而使用工厂模式
为什么使用
在实际业务中我们经常创建对象,常用的做法就是new。但是有些情况下对new不做控制就会造成资源浪费,不能做到多路复用,而且很容易达到系统瓶颈。
还有一个简单的场景:我们在大街上的面馆能看到很多面,比如牛肉面、烩面、方便面,这么多不同种类的面,然而我们只需要告诉面馆需要什么面,让面馆去造,也不需要知道其中的具体实现,面馆里面的厨师一一对应某种类型的面(具体工厂),然后面馆(接口)只需要让具体工厂去做给我们即可。
再举一个买车的例子:
- 某客户想要购买一辆车,他要联系4S店,首先得有4S店(抽象工厂)的电话。
- 客户上网查询(建造工厂),发现了宝马4S店(具体工厂)的电话和奔驰4S店(具体工厂)的电话。
- 客户拨通了宝马4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BMW 320、BMW 530,BMW 740)。
- 客户拨通了奔驰4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BenzC200、BenzE300)。
从以上可以看出:
- 解耦:把对象的创建和使用的过程分开
- 降低代码重复:如果创建某个对象的过程很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
- 降低维护成本:由于创建过程都由工厂统一管理,所有发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
简单说就是为了提供代码结构的扩展性,屏蔽每⼀个功能类中的具体实现逻辑。让外部可以更更加简单的 只是知道调⽤用即可,同时,这也是去掉众多 ifelse 的⽅方式。当然这可能也有⼀些缺点,⽐比如需要实现 的类非常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使⽤中,逐步降低。
概念
⼯⼚模式⼜称⼯厂⽅法模式,是⼀种创建型设计模式,其在父类中提供⼀个创建对象的方法,允许⼦类决定实例化对象的类型。
这种设计模式也是Java开发中最常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

优点
那它有什么优点呢?
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体表现,调用者只关心产品的接口。
缺点
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程序上增加了系统的复杂度,同时也增加了系统具体类的依赖。
工厂模式的使用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道图具体产品类的类名,只需要知道所对应的工厂即可,具体你的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- ...
实战:模拟饭店制作多种类型的食品
我们可以实战模拟多种奖品发放,从这个过程中感受一下工厂方法模式。

其实很简单,我们去一个饭店吃饭,饭店提供了三种类型的食品,分别是:
- 粥
- 面条
- 米饭
咱们就演示一下为什么需要采用工厂模式,咱们从最基本的开始。
罗列一下目录:
↳ tree -L 1 22:07:11
├── demo0100 // 饭馆提供不同类型的食品制作的业务逻辑
├── demo0101 // 不使用工厂模式是什么样子
├── demo0102 // 使用之后,有什么变化
└── go.mod
复制代码
demo0100// 饭馆提供不同类型的食品制作的业务逻辑demo0101// 不使用工厂模式是什么样子demo0102// 使用之后,有什么变化
首先,在demo0100文件夹中分别有三种类型的食品,如:
├── congee
│ └── congee_service.go
├── noodles
│ └── noodles_service.go
└── rice
└── rice_service.go
复制代码
首先,我们看制作粥的业务逻辑,注意,此项目是基于go实现
package congee
import "fmt"
type CongeeService struct{}
func NewCongeeService() *CongeeService {
return &CongeeService{}
}
// 制作皮蛋粥
func (c *CongeeService) MakePreservedEggCongee(orderId int) {
fmt.Printf("模拟制作皮蛋粥: %v \n", orderId)
}
// 制作瘦肉粥
func (c *CongeeService) MakeLeanMeatCongee(orderId int) {
fmt.Printf("模拟制作瘦肉粥: %v \n", orderId)
}
复制代码
从代码中可以看出,粥包含了两种,分别是皮蛋粥、瘦肉粥的制作,那么,我们再看面条和米饭制作业务逻辑:
面条(noodles_service.go):
package noodles
import (
"fmt"
)
type NoodlesService struct{}
func NewNoodlesService() *NoodlesService {
return &NoodlesService{}
}
// 牛肉面的制作方式
func (n *NoodlesService) MakeBeefNoodles(orderId int) {
fmt.Printf("模拟制作牛肉面: %v \n", orderId)
}
复制代码
米饭(rice_service.go):
package rice
import "fmt"
type RiceService struct{}
func NewRiceService() *RiceService {
return &RiceService{}
}
func (r *RiceService) MakeBigDishChickenMixedRice(orderId int) {
fmt.Printf("模拟制作大盘鸡拌饭: %v \n", orderId)
}
复制代码
那么,有了这些业务逻辑,我们是不是开始写路由控制器了?(restaurant_handler.go)
package demo0101
import (
"design-demo/demo0100/congee"
"design-demo/demo0100/noodles"
"design-demo/demo0100/rice"
"fmt"
)
// 省略了很多细节
type RestaurantHandler struct{}
func NewRestaurantHandler() *RestaurantHandler {
return &RestaurantHandler{}
}
type Res struct {
FoodType int
FoodId int
OrderId int
}
func (*RestaurantHandler) FoodToUser(res *Res) {
// 业务逻辑开始
fmt.Println("食品发放开始:", res.OrderId)
// 按照不同类型方法商品 1 粥 2 面条 3 米饭
if res.FoodType == 1 {
// 假设又传了具体粥的序列号
if res.FoodId == 1 {
congee.NewCongeeService().MakePreservedEggCongee(res.OrderId)
} else if res.FoodId == 2 {
congee.NewCongeeService().MakeLeanMeatCongee(res.OrderId)
}
} else if res.FoodType == 2 {
noodles.NewNoodlesService().MakeBeefNoodles(res.OrderId)
} else if res.FoodType == 3 {
rice.NewRiceService().MakeBigDishChickenMixedRice(res.OrderId)
}
fmt.Println("发放成功:", res.OrderId)
}
// 如果有了工厂模式, 实际上,只需要在工厂内部添加if else , 若真想也在工厂内部去掉if else,也可以使用map
复制代码
测试代码,就不再贴上,看图:

那么,如果是工厂方法模式,该如何做呢?
首先,我们有一个通用实现接口ICommodity.go
package store
type Res struct {
FoodType int
FoodId int
OrderId int
}
type ICommodity interface {
// MakeCommodity
MakeCommodity(res *Res)
}
复制代码
然后,让三种类型的食品的制作实现该方法
type CongeeCommodityService struct {
CongeeService *congee.CongeeService
}
func NewCongeeCommodityService() *CongeeCommodityService {
return &CongeeCommodityService{
CongeeService: congee.NewCongeeService(),
}
}
func (c *CongeeCommodityService) MakeCommodity(res *Res) {
if res.FoodId == 1 {
c.CongeeService.MakePreservedEggCongee(res.OrderId)
} else if res.FoodId == 2 {
c.CongeeService.MakeLeanMeatCongee(res.OrderId)
}
}
type NoodlesCommodityService struct {
NoodleService *noodles.NoodlesService
}
func NewNoodlesCommodityService() *NoodlesCommodityService {
return &NoodlesCommodityService{
NoodleService: noodles.NewNoodlesService(),
}
}
func (n *NoodlesCommodityService) MakeCommodity(res *Res) {
if res.FoodId == 1 {
n.NoodleService.MakeBeefNoodles(res.OrderId)
}
}
type RiceCommodityService struct {
RiceService *rice.RiceService
}
func NewRiceCommodityService() *RiceCommodityService {
return &RiceCommodityService{
RiceService: rice.NewRiceService(),
}
}
func (r *RiceCommodityService) MakeCommodity(res *Res) {
if res.FoodId == 1 {
r.RiceService.MakeBigDishChickenMixedRice(res.OrderId)
}
}
复制代码
最重要的工厂方法模式来了,请看store_factory.go
type StoreFactory struct {
Res *store.Res
}
func NewStoreFactory(res *store.Res) *StoreFactory {
return &StoreFactory{
Res: res,
}
}
func (s *StoreFactory) GetCommodityService() store.ICommodity {
if s.Res.FoodType == 1 {
return store.NewCongeeCommodityService()
}
if s.Res.FoodType == 2 {
return store.NewNoodlesCommodityService()
}
if s.Res.FoodType == 3 {
return store.NewRiceCommodityService()
}
return nil
}
复制代码
可以在控制器中使用该方法,我们直接测试一下,看图即可:

假设:
- 如果这里再次添加新的业务类型,比如4 菜类:清炒肉丝, 那你是不是继续在handler中 else if ?
- 如果有了工厂方法模式,这样即使增加了业务逻辑,改动的代码少很多,只需要写对应的实现制作方法的接口即可。
所以也能体现最开始说的优缺点,我在这里提一下把:
- 解耦:把对象的创建和使用的过程分开
- 降低代码重复:如果创建某个对象的过程很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
- 降低维护成本:由于创建过程都由工厂统一管理,所有发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程序上增加了系统的复杂度,同时也增加了系统具体类的依赖。
小结
- 写了个生活小例子-餐馆,来验证我们最开始的理论
- 我们需要慢慢的品尝和欣赏工厂模式,因为很多情况下都能使用该思想,建议多思考为什么要这么做,如何举一反三




近期评论