在类库设计里折腾对象创建的姿势多了,factory 这个词却总被理解得似是而非。有人一提起工厂构造函数就想到设计模式里的工厂方法,有人干脆把它当成普通构造函数用。我最近整理手头组件库的底层模块,仔细把命名构造函数、静态工厂方法、简单工厂、抽象工厂这些概念捋了一遍,发现真正把 factory 在类库场景里用明白,比单纯背定义有用得多。这篇就用实际项目里的代码和踩坑经历,把类库中工厂构造函数的前因后果、实现路径和取舍逻辑讲清楚。
想先说明白:
factory在编程语境下有两条线。一条是类库API设计层面,指“类里特殊的构造函数形态”,例如 Dart 的factory关键字、TypeScript 里的静态工厂方法;另一条是架构层面的工厂模式家族。两者经常被混为一谈,但类库使用者接触最多的其实是前者。这篇文章两条线都会谈,但主线放在“类库设计者如何用工厂构造函数把对象创建过程封装得更好”。
1. 从一次接口混乱的线上事故说起
起因是负责的 SDK 里有个 UserSession 类,原本只有一个普通构造函数,所有调用方直接 new UserSession()。后来产品要求会话对象支持多端登录状态恢复,同一个底层数据要能生成普通会话、游客会话、加密会话三种形态。我图省事直接加了两个可选参数,结果上线后混乱不断:有人传了无效的参数组合,有人误把游客会话当成正式会话用,还有人直接改内部字段绕过校验。
排查到最后,问题根源就是:类库对外暴露了过于灵活的构造函数,使用者根本不知道哪些参数搭配是合法状态。数据模型本身没问题,是创建入口的设计出了问题。这个案例让我下决心把对象创建逻辑收拢起来,而工厂构造函数恰好是解决这类“创建入口失控”最自然的工具。
如果只是把构造函数参数变少、让调用方传入一个配置对象,表面上能减少参数混乱,但配置对象里的字段关系依然没人把关。真正可靠的做法是让“创建对象”这件事本身变成一个有名字、有校验、有语义的操作。这其实就是工厂构造函数的核心价值:把 new 的主动权交给类自己管理,调用方只描述“我想要什么”,不关心“怎么拼出来的”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 理解类库中的 factory:不只是一个关键字
2.1 工厂构造函数与普通构造函数的本质差异
普通构造函数做的事情非常机械:分配内存、初始化字段、返回实例。调用方写 Foo() 时,默认就是在说“用这份参数直接组装一个 Foo”。工厂构造函数则接管了“组装”的决定权,它不保证每次一定返回新对象,也不保证返回对象的运行时类型和调用方写的类名完全一致。
以 Dart 为例,factory 关键字标记的构造函数没有 this,不能访问实例成员,因为它在对象创建的中途介入。但正是这种“中间人”身份,让它能:
- 返回缓存中的已有实例
- 根据入参返回不同子类
- 在对象创建前执行校验逻辑
- 延迟初始化某些复杂资源
我常用的判断标准是:如果构造函数体里出现了 if、return、缓存 query、类型判断,就说明它应该是一个 factory,而不是普通构造函数。普通构造函数撑不起这些逻辑,硬塞进去只会让别人读代码时一头雾水。
2.2 静态工厂方法与构造函数的取舍逻辑
没有 factory 关键字的语言(例如 Java、C#、TypeScript),一般通过静态方法承担类似职责:Session.createGuest()、Session.restoreFromStorage(data)。静态工厂方法和 factory 构造函数在意图上几乎一致,但对类库 API 的阅读感受不同。
构造函数形态给调用方的暗示是“我在 new 一个对象”,静态方法形态则更像“我在执行一个创建行为”。从演进角度看,静态工厂方法的好处是不需要占用构造函数的名额,可以无限增加命名清晰的创建入口;缺点也很明显:继承场景下,子类想要覆盖静态方法会比较别扭,而且 IDE 自动补全时习惯找构造函数,静态方法需要额外文档引导。
在 Dart 里有第三条路:同时提供命名构造函数和 factory。命名构造函数长这样:Session.guest(),它比 SessionFactory.createGuest() 少一层包装,阅读起来更像类本身的创建协议。但命名构造函数如果要返回子类或缓存实例,就必须用 factory 标记。
2.3 工厂模式家族在类库中的真实定位
设计模式里的“工厂方法”和“抽象工厂”,在类库内部实现中用得比对外 API 更频繁。对外暴露的工厂构造函数通常只是一个简化的门面,内部根据情况委托给更复杂的工厂策略。
举个例子,我的组件库里有一个 Renderer 组件,用来把不同业务数据结构渲染成不同卡片。对外只需要 <Renderer data={data} />,内部创建组件树的逻辑却涉及视频渲染器、图文渲染器、广告渲染器。这里的类库用户不需要知道渲染器家族怎么组织,他们只需要调用工厂构造函数 `Renderer.fromD
