工厂模式真正的动态性在于通过反射(如Java的Class.forName()或Python的importlib)从配置中加载类并实例化,而非硬编码if-else分支,从而满足开闭原则、支持运行时类型确定、便于测试与模块隔离。

直接说结论:用反射代替硬编码的 new,把类名、包名、程序集这些信息从配置或参数里读出来,再通过 Class.forName()(Java)或 importlib.import_module()(Python)加载并实例化——这才是工厂模式真正“动态”的关键。
为什么不能只靠 if-else 工厂?
纯手工写 if (type == "A") return new ProductA(); 的工厂,本质是静态分支,每次加新产品都要改工厂代码,违反开闭原则。更麻烦的是,它无法应对运行时才确定类型的情况,比如从 HTTP 请求头、数据库字段、YAML 配置里拿到的类名字符串。
- 编译期就锁死了依赖,新增类要重新编译整个工厂
- 测试困难:没法 mock 一个“还没写出来的类”来跑单元测试
- 模块隔离失效:工厂类必须 import 所有产品类,导致 jar 包体积膨胀、启动变慢
Java 中用 Class.forName() + getDeclaredConstructor().newInstance()
这是最常用也最容易出错的组合。注意不是所有构造器都能直接调用,尤其当目标类只有私有或带参构造器时。
- 必须确保类路径正确:
Class.forName("com.example.PaymentService")中的字符串必须和实际.class文件在 classpath 下的位置完全一致 - 无参构造器不是默认存在:如果类没显式定义无参构造器,且已有带参构造器,
getDeclaredConstructor()会抛NoSuchMethodException - 访问控制要放开:
constructor.setAccessible(true)必须加,否则私有构造器无法调用 - 异常链要完整捕获:
ClassNotFoundException、NoSuchMethodException、IllegalAccessException、InvocationTargetException都得兜住,否则上层直接崩
示例片段:
public static <T> T createInstance(String fullClassName, Class<T> expectedType) {
try {
Class<?> clazz = Class.forName(fullClassName);
Constructor<?> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
return expectedType.cast(ctor.newInstance());
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate " + fullClassName, e);
}
}
Python 中避免 eval(),优先用 importlib
很多人图省事写 eval(f"{class_name}()"),这极其危险——只要配置文件被篡改,就能执行任意代码。正确做法是拆解模块路径,再导入。
- 类名字符串必须带完整模块路径,例如
"payments.alipay.AlipayClient",不能只写"AlipayClient" - 用
importlib.import_module("payments.alipay")导入模块,再用getattr(module, "AlipayClient")拿到类对象 - 如果类在
__init__.py里做了重导出(如from .alipay import AlipayClient),那模块路径就得对应调整 - 务必检查
hasattr(module, class_name),否则getattr抛AttributeError会暴露内部结构
示例片段:
import importlib
def create_instance(full_class_path: str):
module_path, class_name = full_class_path.rsplit(".", 1)
module = importlib.import_module(module_path)
cls = getattr(module, class_name)
return cls()
配置驱动的工厂必须处理类加载失败的降级逻辑
生产环境里,配置写错、jar 包漏发、模块名拼错太常见。不能让一次创建失败导致整个服务不可用。
- 不要在静态块里一次性加载所有类(如
CoffeeFactory示例),容易启动失败;应改为懒加载 + 缓存 - 对每个类名做独立 try-catch,失败时记录 warn 日志,并返回 null 或 fallback 实例,而不是 throw
- 缓存
Class对象而非实例,避免重复反射开销;但注意 ClassLoader 隔离问题(热部署、OSGi 场景下可能加载多次) - 如果业务允许,可配置 fallback 类名,比如
"fallback.DefaultProcessor",作为兜底策略
最易被忽略的一点:反射创建的对象,其构造器里的初始化逻辑(比如连接池、线程池、配置校验)依然会执行。这意味着——你没省掉任何初始化成本,只是把“写死在哪”这个决策点,从源码挪到了配置文件里。别误以为用了反射就等于“轻量”。

















