23、三种模式整合(二):工厂模式创建观察者对象(动态创建不同类型的Observer)
好,我们接着上一章继续聊。上一章我们把单例模式塞进了事件管理器,保证了全局只有一个调度中心。但有个问题一直卡着我——每次添加观察者,都得手动 new 一个具体的 Observer 类。代码写起来又臭又长,而且一旦要换观察者类型,就得改业务代码。
这显然不符合开闭原则。我当时在一个物联网项目中就吃过这个亏——设备类型从 3 种涨到 12 种,每次新增一种,我都得去改订阅逻辑,改得我头皮发麻。后来我悟了:用工厂模式来创建观察者对象,把“创建”这件事抽象出来。
为什么需要工厂来创建观察者?
你想想看,观察者模式里,Subject 只负责通知,它不关心观察者是谁。但我们的业务代码里,经常要根据不同条件创建不同类型的观察者。比如:
- 用户登录成功 → 创建
EmailObserver - 用户登录失败 → 创建
SmsObserver - 用户异地登录 → 创建
PushObserver
如果把这些 if-else 散落在各处,后期维护就是一场噩梦。我见过一个项目,光观察者创建逻辑就写了 200 多行 switch-case,改一个分支要翻半天。
先看一个反例
这是我在代码评审里经常看到的写法:
// 反例:散落在各处的创建逻辑
if (eventType.equals("LOGIN_SUCCESS")) {
eventManager.addObserver(new EmailObserver("admin@example.com"));
} else if (eventType.equals("LOGIN_FAIL")) {
eventManager.addObserver(new SmsObserver("13800138000"));
} else if (eventType.equals("ABNORMAL_LOGIN")) {
eventManager.addObserver(new PushObserver("device_token_abc"));
}
这段代码有什么问题?
- 每次新增观察者类型,都要改这段 if-else
- 观察者的构造参数硬编码在业务逻辑里
- 无法统一管理观察者的生命周期
说白了,就是创建逻辑和业务逻辑耦合在一起。我曾经在一个金融项目中,因为这种写法导致测试环境配置和线上配置不一致,观察者创建错了,结果线上报警短信发到了测试手机号上……嗯,那个月绩效被扣了。
工厂模式登场:ObserverFactory
我们来设计一个工厂类。它的职责很纯粹:根据传入的配置,返回一个 Observer 实例。
public class ObserverFactory {
// 我个人习惯用枚举来定义观察者类型,清晰且不易出错
public enum ObserverType {
EMAIL, SMS, PUSH, LOG
}
public static Observer createObserver(ObserverType type, String target) {
switch (type) {
case EMAIL:
return new EmailObserver(target);
case SMS:
return new SmsObserver(target);
case PUSH:
return new PushObserver(target);
case LOG:
return new LogObserver(target);
default:
throw new IllegalArgumentException("未知的观察者类型: " + type);
}
}
// 重载:支持从配置文件读取参数
public static Observer createObserverFromConfig(String configJson) {
// 解析 JSON,提取 type 和 target
// 然后调用 createObserver
// 实际项目中我会用 Jackson 或 Gson
return createObserver(ObserverType.EMAIL, "default@example.com");
}
}
动态创建:从配置文件驱动
工厂模式最大的威力,在于它可以和配置系统结合。我在一个微服务网关项目中,把观察者的配置写在了 YAML 文件里:
observers:
- type: EMAIL
target: "ops@company.com"
events: ["SERVICE_DOWN", "SERVICE_RECOVERY"]
- type: SMS
target: "13800138000"
events: ["SERVICE_DOWN"]
- type: PUSH
target: "device_token_xyz"
events: ["SERVICE_DOWN", "HIGH_LATENCY"]
启动时,程序读取这个配置,循环调用工厂方法:
// 伪代码:从配置动态创建观察者
for (ObserverConfig config : configList) {
Observer observer = ObserverFactory.createObserver(
ObserverType.valueOf(config.getType()),
config.getTarget()
);
// 注册到事件管理器(单例)
EventManager.getInstance().addObserver(observer, config.getEvents());
}
这样做的好处是什么?新增一种观察者,只需要改配置文件,不需要改代码。我曾经在凌晨三点被叫起来加一种报警方式,我远程改了配置文件,重启服务,搞定。全程没碰一行 Java 代码。
结合单例:工厂本身也可以是单例
如果工厂内部有缓存(比如避免重复创建相同的观察者),或者工厂需要加载资源,我建议把工厂也做成单例。不过大多数情况下,纯静态工厂方法就够用了。
public class ObserverFactory {
private static final Map<String, Observer> cache = new ConcurrentHashMap<>();
private ObserverFactory() {} // 私有构造
public static Observer getOrCreate(ObserverType type, String target) {
String key = type.name() + ":" + target;
return cache.computeIfAbsent(key, k -> createObserver(type, target));
}
private static Observer createObserver(ObserverType type, String target) {
// 创建逻辑同上
}
}
removeObserver() 方法,或者使用 WeakHashMap。
三种模式整合后的调用链路
现在我们把三种模式串起来,看看完整的调用流程:
- 配置加载阶段: 读取 YAML/JSON 配置文件,得到观察者列表
- 工厂创建阶段: ObserverFactory 根据配置动态创建 Observer 实例
- 注册阶段: 将 Observer 注册到 EventManager(单例)中
- 运行阶段: 业务代码触发事件,EventManager 通知所有相关的 Observer
这个链路里,单例保证了全局唯一的事件中心,工厂保证了观察者的灵活创建。两者配合,天衣无缝。
SVG 流程图:三种模式整合后的观察者创建流程
避坑指南:工厂创建观察者时的常见问题
我在实际项目中踩过几个坑,分享给你:
- 观察者创建太频繁: 如果每次事件触发都 new 一个观察者,内存会暴涨。我建议在工厂里加缓存,或者让观察者实现
Disposable接口,用完即销毁。 - 构造参数不一致: 不同观察者的构造参数可能完全不同。EmailObserver 要邮箱地址,SmsObserver 要手机号。我的做法是统一用
Map<String, String>传参,在工厂内部解析。 - 工厂异常处理: 如果创建失败(比如配置错误),不要静默吞掉异常。我习惯在工厂里抛出自定义的
ObserverCreationException,上层可以统一处理。
嗯,这一章就到这里。三种模式整合的威力,你已经看到了。下一章我们会继续深入,看看如何用策略模式进一步优化观察者的行为逻辑。