设计模式-工厂模式

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

工厂方法模式(Factory Method Pattern)

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

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

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

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

factory.png

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

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?。但基本上没有可维护性、扩展性,也不能方便的进行复用。为了实现高内聚低耦合的目标,我们做一些修改:

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分支条件的,修改原有的类违反了开闭原则,这时候就该工厂方法模式出场了:

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)

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

absstact-factory.png

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

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行代码可不是一回事,减少修改!=不修改。)至于判断逻辑的问题,可以使用反射来解决:

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. 工厂方法模式用来创建一个抽象产品,具体工厂实现工厂方法来创建具体产品,抽象工厂模式用组合来创建一个产品家族的抽象类型。