Skip to content

简单工厂

概述

简单工厂(Simple Factory) 通常不被认为是正式的设计模式,而更像是一种编程习惯或简单的创建对象的方式。它不是GOF (Gang of Four) 定义的23 种设计模式之一。

模式结构

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品的具体实现,提供了具体的产品对象。
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。

实现

简单工厂的UML类图如下所示:

接下来是简单工厂具体的实现代码,如下:

首先是 coffee.go 文件,定义了咖啡抽象接口和具体的咖啡类,如下所示:

go
package simplefactory

import "fmt"

// 工厂生成的产品

// Coffee 咖啡接口
type coffee interface {
	name() string
	addMilk()
	addSugar()
}

// Latte 拿铁咖啡
type Latte struct {
}

func (l *Latte) name() string {
	return "拿铁咖啡"
}

func (l *Latte) addMilk() {
	fmt.Printf("%s加牛奶\n", l.name())
}

func (l *Latte) addSugar() {
	fmt.Printf("%s加糖\n", l.name())
}

// Americano 美式咖啡
type Americano struct {
}

func (a *Americano) name() string {
	return "美式咖啡"
}

func (a *Americano) addMilk() {
	fmt.Printf("%s加牛奶\n", a.name())
}

func (a *Americano) addSugar() {
	fmt.Printf("%s加糖\n", a.name())
}

接下来是 factory.go 文件,定义了简单工厂类,如下所示:

go
package simplefactory

import "fmt"

// 生产产品的工厂

// SimpleFactory 简单工厂
type SimpleFactory struct {
}

// CreateCoffee 根据指定的类型创建咖啡
func (sf *SimpleFactory) CreateCoffee(coffeeType string) (coffee, error) {
	switch coffeeType {
	case "latte":
		return &Latte{}, nil
	case "Latte":
		return &Latte{}, nil
	case "americano":
		return &Americano{}, nil
	case "Americano":
		return &Americano{}, nil
	default:
		return nil, fmt.Errorf("不支持的咖啡类型: %s", coffeeType)
	}
}

// CoffeeStore 咖啡店
type CoffeeStore struct {
	factory *SimpleFactory
}

// NewCoffeeStore 创建一个新的咖啡店
func NewCoffeeStore(factory *SimpleFactory) *CoffeeStore {
	return &CoffeeStore{
		factory: factory,
	}
}

// OrderCoffee 顾客点单
func (cs *CoffeeStore) OrderCoffee(coffeeType string) (coffee, error) {
	coffee, err := cs.factory.CreateCoffee(coffeeType)
	if err != nil {
		return nil, err
	}

	// 可以在这里添加一些通用的准备步骤

	return coffee, nil
}

编写单元测试 factory_test.go,如下所示:

go
package simplefactory

import (
	"fmt"
	"testing"
)

// TestCreateLatte 点单拿铁咖啡
func TestCreateLatte(t *testing.T) {
	// 创建咖啡店,并注入一个简单工厂实例
	coffeeStore := NewCoffeeStore(&SimpleFactory{})

	// 点一杯拿铁咖啡
	latte, err := coffeeStore.OrderCoffee("latte")
	if err != nil {
		t.Fatalf("订购拿铁失败: %v", err)
	}
	if latte.name() != "拿铁咖啡" {
		t.Errorf("期望的咖啡是 '拿铁咖啡', 但得到的是 '%s'", latte.name())
	}
	fmt.Printf("成功订购: %s\n", latte.name())
	latte.addMilk()
	latte.addSugar()
}

// TestCreateAmericano 点单美式咖啡
func TestCreateAmericano(t *testing.T) {
	// 创建咖啡店,并注入一个简单工厂实例
	coffeeStore := NewCoffeeStore(&SimpleFactory{})

	// 点一杯美式咖啡
	americano, err := coffeeStore.OrderCoffee("americano")
	if err != nil {
		t.Fatalf("订购美式失败: %v", err)
	}
	if americano.name() != "美式咖啡" {
		t.Errorf("期望的咖啡是 '美式咖啡', 但得到的是 '%s'", americano.name())
	}
	fmt.Printf("成功订购: %s\n", americano.name())
	americano.addMilk()
	americano.addSugar()
}

// TestCreateMocha 点单其他类型的咖啡
func TestCreateOtherTypeCoffee(t *testing.T) {
	// 创建咖啡店,并注入一个简单工厂实例
	coffeeStore := NewCoffeeStore(&SimpleFactory{})

	// 点一杯美式咖啡
	americano, err := coffeeStore.OrderCoffee("mocha")
	if err != nil {
		t.Fatalf("订购美式失败: %v", err)
	}
	if americano.name() != "美式咖啡" {
		t.Errorf("期望的咖啡是 '美式咖啡', 但得到的是 '%s'", americano.name())
	}
	fmt.Printf("成功订购: %s\n", americano.name())
	americano.addMilk()
	americano.addSugar()
}

工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

优缺点

优点

  • 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点

  • 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

参考资料

根据 MIT 许可证发布