我学设计模式-工厂模式我学设计模式-工厂模式

我学设计模式-工厂模式

说起工厂模式呀,就想起当时看Spring源码的心情,难受至极,还不是因为优秀的框架总是伴随着那么多的设计模式。

学生时代上课也没有见过要学习设计模式这门课呀,再说鄙人也是非科班出身,更是没学过设计模式,所以当时看源码,一把辛酸泪。

属实没有办法,只好补好基本功,这不,毕业了,有时间可以重头学习一下设计模式,嗯... 就好像学生时代有时间就会去学习设计模式...

话收回来,我们认真开始了哈,进入主题。

先说说因为什么,而使用工厂模式

为什么使用

在实际业务中我们经常创建对象,常用的做法就是new。但是有些情况下对new不做控制就会造成资源浪费,不能做到多路复用,而且很容易达到系统瓶颈。

还有一个简单的场景:我们在大街上的面馆能看到很多面,比如牛肉面、烩面、方便面,这么多不同种类的面,然而我们只需要告诉面馆需要什么面,让面馆去造,也不需要知道其中的具体实现,面馆里面的厨师一一对应某种类型的面(具体工厂),然后面馆(接口)只需要让具体工厂去做给我们即可。

再举一个买车的例子:

  • 某客户想要购买一辆车,他要联系4S店,首先得有4S店(抽象工厂)的电话。
  • 客户上网查询(建造工厂),发现了宝马4S店(具体工厂)的电话和奔驰4S店(具体工厂)的电话。
  • 客户拨通了宝马4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BMW 320、BMW 530,BMW 740)。
  • 客户拨通了奔驰4S店的电话(获取具体工厂),发现目前店里可以提供(生产)多款车型(具体产品)供客户选择(BenzC200、BenzE300)。

从以上可以看出:

  1. 解耦:把对象的创建和使用的过程分开
  2. 降低代码重复:如果创建某个对象的过程很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
  3. 降低维护成本:由于创建过程都由工厂统一管理,所有发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。

简单说就是为了提供代码结构的扩展性,屏蔽每⼀个功能类中的具体实现逻辑。让外部可以更更加简单的 只是知道调⽤用即可,同时,这也是去掉众多 ifelse 的⽅方式。当然这可能也有⼀些缺点,⽐比如需要实现 的类非常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使⽤中,逐步降低。

概念

⼯⼚模式⼜称⼯厂⽅法模式,是⼀种创建型设计模式,其在父类中提供⼀个创建对象的方法,允许⼦类决定实例化对象的类型。

这种设计模式也是Java开发中最常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

工厂模式-摘抄-重学设计模式

优点

那它有什么优点呢?

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体表现,调用者只关心产品的接口。

缺点

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程序上增加了系统的复杂度,同时也增加了系统具体类的依赖。

工厂模式的使用场景

  1. 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道图具体产品类的类名,只需要知道所对应的工厂即可,具体你的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
  2. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等。
  3. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  4. ...

实战:模拟饭店制作多种类型的食品

我们可以实战模拟多种奖品发放,从这个过程中感受一下工厂方法模式。

design-工厂方法模式-xHvRl7

其实很简单,我们去一个饭店吃饭,饭店提供了三种类型的食品,分别是:

  1. 面条
  2. 米饭

咱们就演示一下为什么需要采用工厂模式,咱们从最基本的开始。

罗列一下目录:

↳ tree -L 1                                                                                                                                         22:07:11   
├── demo0100 // 饭馆提供不同类型的食品制作的业务逻辑
├── demo0101 // 不使用工厂模式是什么样子
├── demo0102 // 使用之后,有什么变化
└── go.mod
复制代码
  1. demo0100 // 饭馆提供不同类型的食品制作的业务逻辑
  2. demo0101 // 不使用工厂模式是什么样子
  3. 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

复制代码

测试代码,就不再贴上,看图:

GFRUki-G2xwuN

那么,如果是工厂方法模式,该如何做呢?

首先,我们有一个通用实现接口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
}
复制代码

可以在控制器中使用该方法,我们直接测试一下,看图即可:

eYcTbQ-wMCeNZ

假设:

  1. 如果这里再次添加新的业务类型,比如4 菜类:清炒肉丝, 那你是不是继续在handler中 else if ?
  2. 如果有了工厂方法模式,这样即使增加了业务逻辑,改动的代码少很多,只需要写对应的实现制作方法的接口即可。

所以也能体现最开始说的优缺点,我在这里提一下把:

  1. 解耦:把对象的创建和使用的过程分开
  2. 降低代码重复:如果创建某个对象的过程很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。
  3. 降低维护成本:由于创建过程都由工厂统一管理,所有发生业务逻辑变化,不需要找到所有需要创建某个对象的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。

缺点:

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程序上增加了系统的复杂度,同时也增加了系统具体类的依赖。

小结

  • 写了个生活小例子-餐馆,来验证我们最开始的理论
  • 我们需要慢慢的品尝和欣赏工厂模式,因为很多情况下都能使用该思想,建议多思考为什么要这么做,如何举一反三

参考