跳到主要内容

工厂模式

问题

Go 中工厂模式是怎么实现的?为什么都用 NewXxx 命名?

答案

工厂函数(Go 惯例)

Go 没有构造函数,惯例用 New 前缀的函数作为工厂:

// 简单工厂函数
type Server struct {
host string
port int
}

func NewServer(host string, port int) *Server {
return &Server{host: host, port: port}
}

工厂方法(返回接口)

// 定义接口
type Cache interface {
Get(key string) (string, error)
Set(key string, value string, ttl time.Duration) error
}

// 不同实现
type redisCache struct { client *redis.Client }
type memoryCache struct { data sync.Map }

// 工厂函数根据类型创建
func NewCache(cacheType string, config Config) (Cache, error) {
switch cacheType {
case "redis":
client := redis.NewClient(&redis.Options{Addr: config.RedisAddr})
return &redisCache{client: client}, nil
case "memory":
return &memoryCache{}, nil
default:
return nil, fmt.Errorf("unsupported cache type: %s", cacheType)
}
}

// 使用
cache, _ := NewCache("redis", config)
cache.Set("key", "value", time.Minute)

注册式工厂

适用于插件化架构,新实现无需修改工厂代码:

// 工厂注册表
var cacheFactories = make(map[string]func(Config) (Cache, error))

func RegisterCache(name string, factory func(Config) (Cache, error)) {
cacheFactories[name] = factory
}

func NewCache(name string, config Config) (Cache, error) {
factory, ok := cacheFactories[name]
if !ok {
return nil, fmt.Errorf("unknown cache: %s", name)
}
return factory(config)
}

// 各实现在 init 中注册
func init() {
RegisterCache("redis", newRedisCache)
RegisterCache("memory", newMemoryCache)
}
标准库中的工厂模式

database/sql 就是注册式工厂:

import _ "github.com/go-sql-driver/mysql" // init() 中调用 sql.Register("mysql", ...)
db, _ := sql.Open("mysql", dsn) // 工厂方法

常见面试问题

Q1: Go 为什么不像 Java 那样用 Factory 类?

答案

  1. Go 有一等函数,工厂函数比工厂类更简洁
  2. Go 没有构造函数,NewXxx() 是社区约定的创建方式
  3. 接口隐式实现,返回接口即可隐藏具体类型

Q2: NewXxx 返回指针还是值?

答案

  • 返回指针 *Xxx:结构体较大、需要修改状态、实现接口方法用指针接收者
  • 返回值 Xxx:小结构体、不可变类型

大多数场景返回指针。

相关链接