简单工厂是函数式封装,抽象工厂是接口+结构体的多产品族管理机制;前者适用于低频新增、编译期确定类型的场景,后者用于强关联组件的原子性切换。

简单工厂和抽象工厂在 go 里根本不是“同级概念”:简单工厂是函数式封装,抽象工厂是接口+结构体组合的多产品族管理机制;选错会导致后期加功能时要么疯狂改 NewXXX 函数,要么硬塞不相关的创建逻辑进一个工厂。
什么时候该用 NewPayment 这类简单工厂?
当你只有一组同类对象(比如所有支付方式都实现 Payment 接口),且新增类型频率低、无需运行时动态注册时,用函数最轻量。
- 典型场景:
log.NewLogger("file")、cache.NewClient("redis")、db.NewConnection("mysql") - 参数必须是编译期可确定的字符串或枚举,不能靠配置文件热加载新类型
- 一旦新增支付方式(如 PayPal),就得改
switch分支 —— 违反开闭原则,但小项目够用 - 别返回具体结构体指针(如
*Alipay),始终返回接口(Payment),否则调用方会意外依赖实现细节
为什么 UIFactory 接口必须定义多个创建方法?
抽象工厂的核心约束是“一组相关对象的原子性创建”,比如 windows 下的 Button 和 checkbox 必须配套使用,不能混搭 macos 的控件。所以工厂接口要强制声明整套能力。
-
CreateButton()和CreateCheckbox()必须由同一个具体工厂实现,确保风格/行为一致 - 新增操作系统支持(如 linux GUI)只需实现新结构体(
LinuxFactory),完全不碰老代码 - 如果只想要按钮,却仍得实现
CreateCheckbox()(哪怕返回nil),说明你误用了抽象工厂——该换回简单工厂或工厂方法 - Go 中没有抽象类,所以用
type UIFactory Interface { ... }+ 具体结构体实现,而非继承
想支持插件化扩展?别写死 switch,用注册表代替
简单工厂的硬编码分支是最大瓶颈;抽象工厂又太重。折中方案是“带注册机制的工厂函数”,既保持函数调用简洁,又满足运行时扩展。
立即学习“go语言免费学习笔记(深入)”;
- 用
map[String]func() Payment存构造函数,而不是switch判断字符串 - 在
init()或模块加载时调用RegisterPayment("paypal", func() Payment { return &PayPal{} }) -
CreatePayment(name)查表失败直接 panic 或返回 Error,避免静默失败 - 注意并发安全:如果注册发生在多个 goroutine,需加
sync.RWMutex,但初始化阶段通常单线程,可省略
var paymentRegistry = map[string]func() Payment{} func RegisterPayment(name string, creator func() Payment) { paymentRegistry[name] = creator } func CreatePayment(name string) Payment { creator, ok := paymentRegistry[name] if !ok { panic("unknown payment method: " + name) } return creator() }
抽象工厂真正发挥作用的地方,是当你需要同时切换多个强关联组件(比如「国内版支付+短信通知」vs「国际版支付+邮件通知」),而简单工厂连这种组合关系都表达不了。别为了设计模式而设计模式——先画清楚你的产品矩阵,再决定用哪一层工厂。