Java 框架中有哪些工厂方法模式的典型应用?
简化版
Java 和常见框架里有很多创建逻辑被工厂封装的例子,例如 Calendar.getInstance()、DocumentBuilderFactory.newInstance()、Spring 的 FactoryBean 和各种连接工厂。面试时重点不是硬说它们都完全等同于 GoF 工厂方法,而是说明它们都通过工厂入口隐藏具体产品创建。
详细版
典型例子可以这样讲:
Calendar.getInstance()根据时区、Locale 等返回合适的Calendar实现,调用方不直接依赖具体日历类;DocumentBuilderFactory.newInstance()先获得工厂,再由工厂创建DocumentBuilder;- JDBC 里
DriverManager.getConnection()屏蔽了具体数据库连接实现; - Spring 的
FactoryBean<T>让一个 Bean 的创建过程由工厂对象控制; - 业务框架中的
ClientFactory、ParserFactory、HandlerFactory常用来隔离 SDK 或协议差异。
需要严谨一点:有些 API 更接近静态工厂或抽象工厂,不一定是标准 GoF 工厂方法结构。面试答案可以说“它们体现了工厂思想”,再指出具体差别,这比强行归类更准确。
完整版教学
一、Java 标准库里的工厂思想
标准库常通过静态方法或工厂类隐藏实现选择。比如 Calendar.getInstance() 返回抽象类型,内部可以根据环境选择具体实现。调用方只写:
Calendar calendar = Calendar.getInstance();
这让调用方无需知道具体日历类怎么创建,也保留了实现替换空间。
二、JAXP 里的工厂对象
XML 解析相关 API 常见两步创建:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
这里先获取工厂,再由工厂创建解析器对象。它体现了工厂封装复杂创建过程的价值,实际结构上也带有抽象工厂和服务发现的味道。
三、Spring 中的 FactoryBean
Spring 的 FactoryBean<T> 可以自定义某个 Bean 的创建逻辑。容器管理的是工厂对象,但对外暴露的是工厂生产出来的对象。
class ClientFactoryBean implements FactoryBean<Client> {
public Client getObject() {
return new Client(/* config */);
}
public Class<?> getObjectType() {
return Client.class;
}
}
这适合第三方客户端、复杂代理对象、需要初始化过程的对象。
四、框架例子要避免绝对化
很多框架 API 并不是教科书式的“抽象创建者 + 具体创建者 + 工厂方法”。例如静态工厂方法不一定等于工厂方法模式,DriverManager 更像注册表加工厂入口。
面试时可以说:这些例子体现了工厂封装创建、返回抽象、隔离具体实现的思想;如果严格按 GoF 分类,要看是否由子类决定创建哪种产品。
五、按结构而不是按方法名给框架 API 分类
看到 getInstance() 或 newInstance() 不能立刻断言是 GoF 工厂方法。应追踪调用者拿到的是产品还是工厂、具体产品由静态分支、服务发现、注册表还是具体 Creator 决定,再说明它更接近哪种工厂结构。
Calendar.getInstance() -> 直接返回 Calendar 产品
DocumentBuilderFactory.newInstance() -> 先返回工厂
factory.newDocumentBuilder() -> 再创建产品
DriverManager.getConnection() -> 查已注册 Driver
FactoryBean.getObject() -> 容器委托工厂对象生产 Bean
这些 API 都隐藏创建细节
但只有满足 Creator 多态结构时才严格称 GoF 工厂方法
这段协作图要回答两个不同问题:产品由谁构造,具体工厂又由谁选择。工厂方法可以把产品构造从业务流程中移走,但应用入口、配置层或容器仍然必须决定使用哪个工厂;声称模式“消灭了所有分支”并不准确。
六、模式边界与扩展成本
| 维度 | 本题结论 |
|---|---|
| 稳定抽象 | API 返回抽象类型并隐藏提供者发现、构造参数或代理生成细节。 |
| 主要变化点 | 平台实现、服务提供者、驱动或容器中产品创建策略。 |
| 新增一种产品的动作 | 按对应 SPI、注册协议或具体工厂扩展,而非一律继承某个 Creator。 |
| 不应放进工厂的职责 | 把所有带 newInstance 名称的方法强行归成同一模式。 |
| 更轻或更合适的方案 | 回答为“工厂思想/静态工厂/注册表/FactoryBean”,并准确指出与 GoF 结构差异。 |
是否符合开闭原则不能只数新增了多少类,而要看高风险旧代码是否仍被反复修改。若每次扩展仍要修改一个中心 switch、产品接口又泄漏具体类型,即使类名叫 Factory,也没有获得工厂方法的主要收益。
七、用三个测试验证设计没有走样
- 契约测试:对每个具体工厂执行同一组产品接口测试,确认返回值满足抽象契约。
- 扩展测试:加入一个假产品和假工厂,记录为了接入它修改了哪些旧文件。
- 未知类型测试:若存在注册表或参数路由,明确缺失 key 的异常类型和降级策略。
- 生命周期测试:连续创建两次,确认产品应当复用还是隔离,避免无意变成单例或重复昂贵连接。
- 依赖测试:具体构造参数应由工厂显式获得,业务类不应通过全局容器偷偷查找。
- 并发测试:注册表若支持运行期写入,必须定义发布、覆盖和读取的一致性;只读注册表则尽量启动期冻结。
- 可观测性测试:记录命中的工厂 key 与具体实现,排查配置选择错误时无需猜测。
记忆钩子:方法名只说明入口,调用链里的扩展点才决定模式。
八、常见误区与追问
- 误区:Calendar.getInstance 是教科书式工厂方法结构。 它更常被归为静态工厂,未必有具体 Creator 子类覆写创建方法。
- 误区:DriverManager 只体现工厂方法。 它还使用驱动注册表和路由选择。
- 误区:FactoryBean 与 BeanFactory 是同一个接口。 前者自定义某个产品创建,后者是 Spring 容器核心工厂。
- 追问:JAXP 为什么先拿工厂再拿 Builder? 实现发现与产品创建被分成两层,并可由提供者替换。
- 追问:举框架例子时怎样保持严谨? 先说体现工厂思想,再按是否有 Creator 多态说明严格分类。
- 追问:FactoryBean 取自身对象怎么做? 在 Spring 中通常使用
&beanName获取工厂本身而不是其产品。
九、加强记忆
框架中的工厂应用不要死背类名,要抓住“隐藏具体实现、统一创建入口、返回抽象类型”三个特征。答题时先讲工厂思想,再谨慎说明它和标准工厂方法结构的差异,会显得更专业。