Go中应避免硬套设计模式,优先考虑依赖注入、窄接口和函数式配置;单例实为带同步保护的全局变量,易致初始化失败与测试困难;工厂应简化为switch或Option函数;接口需遵循隔离原则。

Go 里不该“深入理解设计模式”,而该清楚什么时候**不写模式**——多数业务代码用不上单例、工厂或观察者,硬套反而增加维护成本。
为什么 Go 的单例模式常被误用
很多人看到 GetInstance() 就觉得“规范”,但 Go 没有类和构造函数,所谓单例本质只是带初始化保护的全局变量。问题在于:
- 用
sync.Once确保线程安全是必要的,但若实例化逻辑依赖未初始化的配置(比如os.Getenv("DB_URL")在init()前未加载),once.Do会静默失败或 panic - 测试时无法替换依赖:一旦
GetDBPool()返回真实连接池,单元测试就绕不开数据库 - 真正需要的是依赖注入,不是单例——把
*sql.DB当参数传给 handler,比调global.GetDB()更清晰、更易测
工厂模式在 Go 中的合理边界
Go 不需要抽象工厂来应对“未来可能加新数据库类型”。实际项目中,工厂往往只服务于两个场景:
- 根据配置字符串(如
"mysql"/"sqlite")返回不同Database实现,此时用简单switch+ 接口即可,无需额外结构体封装 - 初始化代价高且需复用(如 TLS 配置、HTTP 客户端),这时应改用
WithXXXOption函数式配置,而不是NewMySQLFactory().Create() - 如果工厂只产出一种类型(比如永远只创建
PostgreSQL),那它就是个冗余 wrapper,直接导出构造函数NewPostgresDB(...)更直白
接口定义要小,嵌入要克制
Go 的接口不是为了“建模领域概念”,而是为了描述“能做什么”。常见错误包括:
立即学习“go语言免费学习笔记(深入)”;
- 定义大而全的
UserServiceInterface(含 12 个方法),结果只有 2 个方法在当前包被用到——这违反了接口隔离原则 - 为复用字段强行嵌入结构体(如
type User struct { BaseModel }),导致测试时必须初始化所有嵌入字段,哪怕只关心ID - 正确做法是按调用方需要定义窄接口,例如
type Reader interface{ Read(id int) error },再让具体类型隐式实现
最易被忽略的一点:Go 的“设计模式”本质是语法特性的自然延伸,不是拿来套的模板。写完一个 func WithTimeout(d time.Duration) Option,比翻《设计模式》找 Strategy 更贴近 Go 的表达习惯。


















