应使用 Factory 函数而非 new(),因其封装参数校验、默认值设置、资源连接等初始化逻辑,避免零值 panic、逻辑分散和测试困难;推荐 NewXxx 命名、返回指针、支持 option 选项模式、返回具体接口类型,并将外部依赖显式注入以保障可测性。

为什么不用 new() 而要写 Factory 函数
因为 new() 只做内存分配,不执行初始化逻辑;而真实对象往往需要校验参数、设置默认值、连接资源、注册回调等。比如创建一个数据库连接对象,你不能只分配内存就完事——得检查 dsn 是否合法、尝试 ping 一下、设置超时时间。Factory 函数就是把这一串“必须做的初始化”收拢到一个入口里,避免每次 new(MyDB{}) 后还要手动调 obj.Init() 或漏掉关键步骤。
常见错误现象:
• 对象字段为零值却没被发现,运行时 panic(比如 http.Client 字段为 nil 导致 Do 失败)
• 初始化逻辑散落在各处,改一个默认超时要搜 5 个文件
• 单元测试难 mock,因为构造过程耦合了外部依赖(如直接调 sql.Open)
实操建议:
• 工厂函数名用 NewXxx 命名惯例(Go 社区共识,IDE 和文档工具都认)
• 返回指针(*MyType),避免复制大结构体,也符合 Go 中可变对象的惯用法
• 如果初始化可能失败,返回 (*MyType, error),别 panic —— 让调用方决定怎么处理错误
• 不在工厂里做重 IO 操作(如真实建连),除非明确这是“创建即就绪”的语义;否则拆成 NewXxx() + Open()
如何让 Factory 支持可选配置(类似 WithXXX 选项模式)
硬编码默认值会降低复用性:比如日志级别、重试次数、超时时间,不同场景要不同配置。但给每个参数都加函数参数太难维护(8 个参数 = 2⁸ 种组合),也不利于扩展。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
• 定义一个不可导出的 option 函数类型:type option func(*MyType)
• 每个配置项封装成一个函数,如 WithTimeout(t time.Duration),内部调用传入的 *MyType 的 setter 或直接赋值字段
• 工厂函数最后按顺序应用所有 option,保证覆盖顺序可控(后传入的 option 优先级更高)
• 避免在 option 函数里做副作用操作(如启动 goroutine、打开文件),它只该负责配置
示例片段:
func NewClient(opts ...option) *Client {<br> c := &Client{timeout: 30 * time.Second}<br> for _, o := range opts {<br> o(c)<br> }<br> return c<br>}
Factory 返回 interface{} 还是具体类型?
返回 interface{} 是反模式。它放弃编译期类型检查,把错误推到运行时,还让 IDE 失去自动补全能力。真正需要抽象的是行为,不是类型擦除。
实操建议:
• 如果多个实现共用同一组方法,定义一个导出的 interface(如 Storer),工厂返回该 interface
• 工厂函数本身可以是具体类型的,比如 NewMemoryStorer() 返回 Storer,NewRedisStorer() 也返回 Storer
• 不要为了“统一返回类型”而造一个空接口或泛型 wrapper;Go 的 interface 是隐式实现的,够用且轻量
• 泛型不是万能解:如果工厂只是创建同构对象(比如各种 Cache[string]),用泛型没问题;但如果行为差异大(如 FileReader vs HTTPReader),interface 更合适
单元测试时 Factory 怎么隔离外部依赖
Factory 函数如果直接调 net.Dial、os.Open 或 time.Now(),会导致测试慢、不稳定、无法覆盖错误路径(比如网络超时)。
实操建议:
• 把依赖项抽成字段或参数:比如把 time.Now 替换为可注入的 clock func() time.Time
• 对于 IO 类依赖(如数据库、HTTP 客户端),工厂接收一个 interface 参数(如 db sql.Queryer),测试时传 mock 实现
• 工厂函数自身不 new 依赖,只组合已有依赖 —— 这样依赖生命周期由上层控制,测试时可精确控制状态
• 避免在 Factory 内部用 init() 或包级变量做单例,那会让测试间相互污染
容易被忽略的一点:Factory 函数的参数命名要体现其可测试性意图。比如不要叫 dsn string,而叫 dbConn DBConnector(哪怕底层还是 string),这样一眼看出“这里可以被替换”。










