Skip to main content
  1. internet/

设计模式之工厂模式

·2061 words·5 mins·

工厂模式,顾名思义,是一种用来生产实例的模式。

工厂模式有三种类别:简单工厂模式、工厂方法模式和抽象工厂模式。

《HeadFirst设计模式》中的披萨场景能够帮助我们循序渐进的了解这三种模式。

再现场景——披萨 #

假设披萨店里有三种披萨:奶酪披萨、希腊披萨和意大利披萨,这些披萨都具有相同的行为——准备原理、烘焙、裁剪、装盒,因此我们抽象出了一个披萨接口:

type Pizza interface {
	Prepare()
	Bake()
	Cut()
	Box()
}

并且三种披萨都实现了这个披萨接口:

// 具体实现方法略
type CheesePizza struct {
}

type GreekPizza struct {
}

type PepperoniPizza struct {
}

那么这时一个用户下单一个披萨的方法就可以是:

type PizzaStore struct {}

func (p PizzaStore) OrderPizza(typ string) Pizza {
	var pizza Pizza
	switch typ {
	case "cheese":
		pizza = new(CheesePizza)
	case "greek":
		pizza = new(GreekPizza)
	case "pepperoni":
		pizza = new(PepperoniPizza)
	}
	pizza.Prepare()
	pizza.Bake()
	pizza.Cut()
	pizza.Box()
	return pizza
}

用户选择了披萨类型,然后OrderPizza就能够返回给用户该类型的披萨。

简单工厂模式-封装对象的创建过程 #

但是,如果此时我们的披萨店里去掉了意大利披萨,并且新增了素食披萨,这时我们要修改OrderPizza为:

type VeggiePizza struct {
}

type PizzaStore struct {}

func (p PizzaStore) OrderPizza(typ string) Pizza {
	var pizza Pizza
	switch typ {
	case "cheese":
		pizza = new(CheesePizza)
	case "greek":
		pizza = new(GreekPizza)
	//case "pepperoni": 删除
	//	pizza = new(PepperoniPizza)
	case "veggie": // 新增
		pizza = new(VeggiePizza)
	}
	pizza.Prepare()
	pizza.Bake()
	pizza.Cut()
	pizza.Box()
	return pizza
}

OrderPizza最大的问题就是将创建披萨的代码和处理披萨的代码耦合在了一起——新增或者删除一个披萨都会对OrderPizza做修改——虽然没有修改处理披萨的流程,但是修改了处理披萨流程所在的方法。

我们可以说OrderPizza违反了开闭原则(没有对修改关闭),也可以说其违反了单一职责原则(同时具有创建披萨和处理披萨两个职责)。但不管我们将其定义为违反了哪些原则,最重要的是我们在扩展代码时,发现了“坏味道”。

这时,我们需要通过简单工厂模式来将创建披萨的代码封装起来。

修改后的代码如下:

type PizzaStore struct{
	factory SimplePizzaFactory
}

func (p PizzaStore) OrderPizza(typ string) Pizza {
	pizza := p.factory.CreatePizza(typ)
	pizza.Prepare()
	pizza.Bake()
	pizza.Cut()
	pizza.Box()
	return pizza
}

type SimplePizzaFactory struct {
}

func (spf SimplePizzaFactory) CreatePizza(typ string) Pizza {
	var pizza Pizza
	switch typ {
	case "cheese":
		pizza = new(CheesePizza)
	case "greek":
		pizza = new(GreekPizza)
	case "veggie":
		pizza = new(VeggiePizza)
	}
	return pizza
}

通过封装创建的过程,使处理披萨和创建披萨的代码得到了隔离,那么这时再新增或者删除一种披萨,就不需要在OrderPizza中修改,避免影响到处理披萨的代码。

工厂方法-让子类决定创建何种对象 #

现在让我们将披萨店开到全世界!

但是这时候有了新问题:每个地区的披萨口味都应该是”因地制宜“的,所以每个披萨店都有自己独特的CreatePizza。

虽然创建披萨的过程变了,但是每个地区处理披萨(准备、烘焙、裁剪、装盒)的流程没变,这时我们需要抽象出一个基础类——这个基础类提供了处理披萨的方法,但是创建披萨的方法需要注入。每个地区的披萨店都实现了自己的创建披萨的方法,并组装基础类。

基础类:

type BasePizzaStore struct {
	store PizzaStore
}

func (bps BasePizzaStore) OrderPizza(typ string) Pizza {
	pizza := bps.store.CreatePizza(typ)
	pizza.Prepare()
	pizza.Bake()
	pizza.Cut()
	pizza.Box()
	return pizza
}

type PizzaStore interface {
	CreatePizza(typ string) Pizza
}

基础类中包含了一个披萨商店,这个商店在运行时提供具体的创建披萨的方法。

再看下两个地区的披萨店实例:


type ChinaPizzaStore struct {
	BasePizzaStore
}

func NewChinaPizzaStore() ChinaPizzaStore {
	store := ChinaPizzaStore{}
	store.BasePizzaStore = BasePizzaStore{store: store}
	return store
}

func (c ChinaPizzaStore) CreatePizza(typ string) Pizza {
	var pizza Pizza
	switch typ {
	case "cheese":
		pizza = new(ChinaCheesePizza)
	case "greek":
		pizza = new(ChinaGreekPizza)
	case "veggie":
		pizza = new(ChinaVeggiePizza)
	}
	return pizza
}

type AmericaPizzaStore struct {
	BasePizzaStore
}

func NewAmericaPizzaStore() AmericaPizzaStore {
	store := AmericaPizzaStore{}
	store.BasePizzaStore = BasePizzaStore{store: store}
	return store
}

func (c AmericaPizzaStore) CreatePizza(typ string) Pizza {
	var pizza Pizza
	switch typ {
	case "cheese":
		pizza = new(AmericaCheesePizza)
	case "greek":
		pizza = new(AmericaGreekPizza)
	case "veggie":
		pizza = new(AmericaVeggiePizza)
	}
	return pizza
}

重点在于每个地区的商店都组装了BasePizzaStore,所以每个商店都统一使用其OrderPizza。并且每个商店都将自己的实例注入到了BasePizzaStore的实例中,也就是在OrderPizza中创建披萨的过程使用的是各个地区商店自己的CreatePizza。

让我们在这两个地区分别下单一个披萨:

func main() {
	chinaStore := NewChinaPizzaStore()
	chinaStore.OrderPizza("cheese")

	americaStore := NewAmericaPizzaStore()
	americaStore.OrderPizza("veggie")
}

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

抽象工厂-通过接口倒置依赖 #

现在我们要对披萨店进一步优化:每个商店都能卖各种地区的披萨,但是味道需要”因地制宜“,所以不同地区的商店制作同一种披萨所使用的原料的分量是不同的!

我们需要先创建一个原料工厂接口和两个具体的地区工厂实现:

type PizzaIngredientFactory interface {
	CreateDough() Dough
	CreateSauce() Sauce
}

type ChinaPizzaIngredientFactory struct {
}

func (c ChinaPizzaIngredientFactory) CreateDough() Dough {
	return Dough{Weight: 10}
}

func (c ChinaPizzaIngredientFactory) CreateSauce() Sauce {
	return Sauce{Weight: 20}
}

type AmericaPizzaIngredientFactory struct {
}

func (c AmericaPizzaIngredientFactory) CreateDough() Dough {
	return Dough{Weight: 10}
}

func (c AmericaPizzaIngredientFactory) CreateSauce() Sauce {
	return Sauce{Weight: 20}
}

然后将原料工厂注入到各种披萨中:

type ChinaCheesePizza struct {
	ingredient PizzaIngredientFactory
	dough      Dough
	sauce      Sauce
}

func NewChinaCheesePizza(ingredient PizzaIngredientFactory) *ChinaCheesePizza {
	return &ChinaCheesePizza{
		ingredient: ingredient,
	}
}

func (c *ChinaCheesePizza) Prepare() {
	c.dough = c.ingredient.CreateDough()
	c.sauce = c.ingredient.CreateSauce()
}

func (c ChinaCheesePizza) Bake() {
}

func (c ChinaCheesePizza) Cut() {
}

func (c ChinaCheesePizza) Box() {
}

再回到我们的商店中,修改创建披萨的代码:

type ChinaPizzaStore struct {
	BasePizzaStore
}

func NewChinaPizzaStore() ChinaPizzaStore {
	store := ChinaPizzaStore{}
	store.BasePizzaStore = BasePizzaStore{store: store}
	return store
}

func (c ChinaPizzaStore) CreatePizza(typ string) Pizza {
  // 创建原料工厂
	ingredient := ChinaPizzaIngredientFactory{}
	var pizza Pizza
	switch typ {
	case "cheese":
		//pizza = new(ChinaCheesePizza) 注入
		pizza = NewChinaCheesePizza(ingredient)
	case "greek":
		//pizza = new(ChinaGreekPizza)
		pizza = NewChinaGreekPizza(ingredient)
	case "veggie":
		//pizza = new(ChinaVeggiePizza)
		pizza = NewChinaVeggiePizza(ingredient)
	}
	return pizza
}

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类——这同样符合依赖倒置原则(依赖接口而不是具体类)——实现了披萨制作与原材料选择/供应的解耦。

总结 #

  1. 简单工厂就是对创建对象过程的简单封装。
  2. 工厂方法的应用场景为:将对象共有的方法抽象为基础对象,但是基础对象依赖于对象,这时可以将对象抽象为接口,基础对象依赖于这个接口。当实例化对象后,再实例化其包含的基础对象。
  3. 抽象工厂的应用场景为:对象拥(披萨和地区商店)有一个产品族(原料),可以通过接口来抽象这个产品族。只有当实例化对象时,才将这个产品族的实例注入到对象中,实现对象与产品族之间的解耦。