工厂模式
问题
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 类?
答案:
- Go 有一等函数,工厂函数比工厂类更简洁
- Go 没有构造函数,
NewXxx()是社区约定的创建方式 - 接口隐式实现,返回接口即可隐藏具体类型
Q2: NewXxx 返回指针还是值?
答案:
- 返回指针
*Xxx:结构体较大、需要修改状态、实现接口方法用指针接收者 - 返回值
Xxx:小结构体、不可变类型
大多数场景返回指针。