Skip to content

工厂方法模式

概述

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,用于定义一个创建对象的接口,但让子类决定具体实例化的类。它的核心思想是将对象的创建过程延迟到子类中,从而实现灵活的对象创建机制。与简单工厂模式相比,工厂方法模式通过抽象工厂接口和多态性实现更高的扩展性,符合开闭原则。

factory-method

模式结构

工厂方法模式的主要角色如下:

  • 抽象产品(Abstract Product):定义产品的接口,规定了具体产品需要实现的方法。在 Go 中通常使用 interface 定义。
  • 具体产品(Concrete Product):实现抽象产品接口的结构体,表示实际创建的对象。
  • 抽象创建者(Abstract Creator):定义工厂方法的接口,声明创建产品的方法,通常返回抽象产品接口类型。在 Go 中,通常使用 interface 定义。
  • 具体创建者(Concrete Creator):实现抽象创建者接口的结构体,负责创建具体产品实例。
  • 客户端(Client):通过抽象创建者接口调用工厂方法来获取产品实例,并使用产品的功能。客户端与抽象创建者和抽象产品交互,无需关心具体实现。

实现

工厂方法案例的UML类图如下所示:

抽象产品和具体产品

coffee.go 代码如下:

go
package factorymethod

import "fmt"

// Coffee 咖啡接口,表示抽象产品
type Coffee interface {
    // Name 获取咖啡名称
    Name() string
    // AddMilk 为咖啡加奶
    AddMilk()
    // AddSugar 为咖啡加糖
    AddSugar()
}

// Latte 表示拿铁咖啡的具体产品
type Latte struct {
    // 字段可根据实际需求定义
}

// Name 获取咖啡名称
func (l *Latte) Name() string {
    return "拿铁咖啡"
}

// AddMilk 加奶
func (l *Latte) AddMilk() {
    fmt.Printf("%s加奶\n", l.Name())
}

// AddSugar 加糖
func (l *Latte) AddSugar() {
    fmt.Printf("%s加糖\n", l.Name())
}

// Americano 表示美式咖啡的具体产品
type Americano struct {
    // 字段可根据实际需求定义
}

// Name 获取咖啡名称
func (a *Americano) Name() string {
    return "美式咖啡"
}

// AddMilk 加奶
func (a *Americano) AddMilk() {
    fmt.Printf("%s加奶\n", a.Name())
}

// AddSugar 加糖
func (a *Americano) AddSugar() {
    fmt.Printf("%s加糖\n", a.Name())
}

抽象工厂和具体工厂

factory.go 代码如下:

go
package factorymethod

import "fmt"

// CoffeeFactory 抽象工厂接口,定义创建咖啡的方法
type CoffeeFactory interface {
    // CreateCoffee 创建特定类型的咖啡
    CreateCoffee() Coffee
}

// LatteFactory 生产拿铁咖啡的工厂
type LatteFactory struct{}

// CreateCoffee 创建一个拿铁咖啡
func (lf *LatteFactory) CreateCoffee() Coffee {
    return &Latte{}
}

// AmericanoFactory 生产美式咖啡的工厂
type AmericanoFactory struct{}

// CreateCoffee 创建一个美式咖啡
func (af *AmericanoFactory) CreateCoffee() Coffee {
    return &Americano{}
}

// CoffeeStore 咖啡店
type CoffeeStore struct {
    factory CoffeeFactory
}

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

// OrderCoffee 顾客点单,返回特定工厂生产的咖啡
func (cs *CoffeeStore) OrderCoffee() (Coffee, error) {
    if cs.factory == nil {
        return nil, fmt.Errorf("factory is nil")
    }
    return cs.factory.CreateCoffee(), nil
}

客户端(单元测试)

client_test.go 代码如下:

go
package factorymethod

import (
    "testing"
)

// TestOrderCoffee 测试咖啡点单功能
func TestOrderCoffee(t *testing.T) {
    // 测试拿铁咖啡工厂
    t.Run("LatteFactory", func(t *testing.T) {
        factory := &LatteFactory{}
        store := NewCoffeeStore(factory)
        coffee, err := store.OrderCoffee()
        if err != nil {
            t.Errorf("expected no error, got %v", err)
        }
        if coffee == nil || coffee.Name() != "拿铁咖啡" {
            t.Errorf("expected Latte, got %v", coffee)
        }
        t.Logf("点了一杯 %s", coffee.Name())
        coffee.AddMilk()
        coffee.AddSugar()
    })

    // 测试美式咖啡工厂
    t.Run("AmericanoFactory", func(t *testing.T) {
        factory := &AmericanoFactory{}
        store := NewCoffeeStore(factory)
        coffee, err := store.OrderCoffee()
        if err != nil {
            t.Errorf("expected no error, got %v", err)
        }
        if coffee == nil || coffee.Name() != "美式咖啡" {
            t.Errorf("expected Americano, got %v", coffee)
        }
        t.Logf("点了一杯 %s", coffee.Name())
        coffee.AddMilk()
        coffee.AddSugar()
    })

    // 测试空工厂
    t.Run("NilFactory", func(t *testing.T) {
        store := NewCoffeeStore(nil)
        coffee, err := store.OrderCoffee()
        if err == nil {
            t.Error("expected error for nil factory, got nil")
        }
        if coffee != nil {
            t.Errorf("expected nil coffee, got %v", coffee)
        }
        t.Logf("无法点单:%v", err)
    })
}

实现说明

工厂方法模式通过抽象工厂接口(CoffeeFactory)和具体工厂(LatteFactoryAmericanoFactory)实现对象的创建,CoffeeStore 作为客户端通过工厂接口获取咖啡实例。相比简单工厂模式,工厂方法模式通过多态性将创建逻辑分散到具体工厂类,新增产品时只需添加新的具体产品类和工厂类,无需修改现有代码,符合开闭原则。

使用场景

工厂方法模式适用于以下场景:

  • 产品类型动态扩展:当产品类型需要频繁扩展时,工厂方法模式通过新增工厂类支持新产品,避免修改现有代码。
  • 数据库驱动:为不同数据库(如 MySQL、PostgreSQL)定义工厂接口,创建对应的驱动实例。
  • 日志记录器:根据日志类型(如文件日志、控制台日志)创建不同的日志记录器。
  • UI 组件:根据平台(如 Windows、Linux)创建不同的界面组件。

优点与缺点

优点

  • 通过抽象工厂接口降低客户端与具体产品类的耦合。
  • 符合开闭原则,新增产品只需添加具体产品类和对应工厂类,无需修改现有代码。
  • 提供灵活的扩展机制,适合产品类型动态变化的场景。

缺点

  • 每增加一种产品都需要新增具体产品类和具体工厂类,增加代码量和系统复杂度。
  • 对于简单场景,工厂方法模式可能显得过于复杂,简单工厂或直接实例化可能更合适。

注意事项

  • Go 设计哲学:Go 强调简单性和显式依赖,工厂方法模式适合产品类型较多且需要动态扩展的场景。对于简单场景,可考虑简单工厂或直接实例化。
  • 接口设计:确保抽象工厂接口简洁,避免过度设计。
  • 错误处理:在 Go 中,工厂方法应返回错误以便客户端处理异常情况。

参考资料

根据 MIT 许可证发布