图解 Go 设计模式 01|单例模式:sync.Once 为什么是 Go 的标准答案
单例是设计模式里被讲得最烂的一个,也是最容易写错的一个。本文聊聊 Go 里为什么 sync.Once 是标准答案,以及更重要的——你真的需要单例吗。
用 Go 的接口、组合、函数和并发习惯重新理解经典设计模式,关注模式背后的工程边界。
适合已经写过 Go,希望把设计模式从名词表变成日常设计判断的工程师。
对象怎么创建、复用和复制。
对象怎么组合、包装和对外暴露。
对象怎么协作、切换和传递请求。
单例是设计模式里被讲得最烂的一个,也是最容易写错的一个。本文聊聊 Go 里为什么 sync.Once 是标准答案,以及更重要的——你真的需要单例吗。
工厂方法被讲成「定义接口创建对象」,但这是描述不是理由。本文聊聊 Go 标准库怎么用 driver.Register 把创建逻辑收敛到一处。
建造者被讲成「分步构建复杂对象」,但大多数文章没说清楚什么时候该用。本文聊聊 Go 里 Functional Options 和链式建造者的取舍。
原型模式被讲成「通过克隆创建对象」,但这个定义在 Go 里几乎没意义——new 一个对象又不贵。本文聊聊原型的真实价值在配置模板、请求骨架等场景。
对象池被讲成「复用对象减少 GC」,但 sync.Pool 的语义比这个微妙得多。本文聊聊什么场景该用对象池,以及 sync.Pool 的常见误用。
适配器被讲成「让不兼容接口协作」,但这个定义模糊到可以套在任何包装层上。本文聊聊适配器真正解决的是「双方都不能改但必须配合」的场景,以及它和门面、代理的边界。
装饰器被讲成「动态叠加功能」,但这个定义套在中间件、责任链、代理上都成立。本文聊聊装饰器的标志特征——洋葱模型,以及它和代理的真实差别。
代理被讲成「控制对象访问」,但控制访问有十几种方式。本文聊聊代理的标志特征——透明拦截,以及四种典型代理(虚拟、保护、缓存、远程)的真实场景。
门面被讲成「简化复杂系统」,但简化有两种——隐藏 vs 合并。本文聊聊门面模式的标志特征是「多对一合并」,以及它跟适配器、中介者的边界。
组合被讲成「树形结构统一处理」,但树形结构有十几种实现方式。本文聊聊组合模式的标志特征——叶子和容器同接口,以及它跟装饰器、迭代器的边界。
策略模式被讲烂了,但绝大多数文章只告诉你「消除 if-else」。这是结果,不是理由。本文聊聊什么时候才真正值得抽策略,以及 map 注册比 switch 强在哪。
责任链被讲成「请求沿链传递」,但传递有两种——拒绝型(审批流)和管道型(中间件)。本文聊聊责任链的标志特征——链上节点自己决定是否处理,以及它和策略、装饰器的边界。
观察者被讲成「一个对象变化通知多个订阅者」,但通知方式有同步、异步、跨进程三种。本文聊聊观察者的标志特征——单向通知,以及它和责任链、Pub-Sub 的边界。
模板方法被讲成「父类定义骨架子类填步骤」,但 Go 没有继承。本文聊聊 Go 里用函数字段实现等效语义的写法,以及它和策略、钩子的边界。
状态模式被讲成「行为随状态变」,但状态和策略结构一模一样。本文聊聊状态的标志特征——状态自己切换状态,以及它和策略、责任链的边界。
命令模式被讲成「请求封装成对象」,但封装请求有什么用?本文聊聊命令的标志特征——支持撤销/排队/日志,以及它和策略、责任链的边界。
迭代器被讲成「统一遍历接口」,但 Go 的 for range 已经是迭代器。本文聊聊迭代器的标志特征——隐藏容器内部结构,以及 Go 1.23 range-over-func 带来的语言级支持。