23、三种模式整合(二):工厂模式创建观察者对象(动态创建不同类型的Observer)

好,我们接着上一章继续聊。上一章我们把单例模式塞进了事件管理器,保证了全局只有一个调度中心。但有个问题一直卡着我——每次添加观察者,都得手动 new 一个具体的 Observer 类。代码写起来又臭又长,而且一旦要换观察者类型,就得改业务代码。

这显然不符合开闭原则。我当时在一个物联网项目中就吃过这个亏——设备类型从 3 种涨到 12 种,每次新增一种,我都得去改订阅逻辑,改得我头皮发麻。后来我悟了:用工厂模式来创建观察者对象,把“创建”这件事抽象出来。

为什么需要工厂来创建观察者?

你想想看,观察者模式里,Subject 只负责通知,它不关心观察者是谁。但我们的业务代码里,经常要根据不同条件创建不同类型的观察者。比如:

  • 用户登录成功 → 创建 EmailObserver
  • 用户登录失败 → 创建 SmsObserver
  • 用户异地登录 → 创建 PushObserver

如果把这些 if-else 散落在各处,后期维护就是一场噩梦。我见过一个项目,光观察者创建逻辑就写了 200 多行 switch-case,改一个分支要翻半天。

核心思路: 把观察者的创建逻辑收拢到工厂里。工厂根据传入的参数(比如事件类型、优先级、配置字符串),动态实例化对应的 Observer 子类。

先看一个反例

这是我在代码评审里经常看到的写法:

// 反例:散落在各处的创建逻辑
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");
    }
}
我的小技巧: 工厂方法可以重载多个版本。一个版本接收枚举+目标地址,另一个版本接收 JSON 字符串或 Properties 对象。这样不管是硬编码还是配置驱动,都能覆盖到。

动态创建:从配置文件驱动

工厂模式最大的威力,在于它可以和配置系统结合。我在一个微服务网关项目中,把观察者的配置写在了 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) {
        // 创建逻辑同上
    }
}
注意: 带缓存的工厂要小心内存泄漏。如果观察者内部持有大对象或者注册了监听器,缓存会导致这些对象无法被 GC。我建议在工厂里加一个 removeObserver() 方法,或者使用 WeakHashMap。

三种模式整合后的调用链路

现在我们把三种模式串起来,看看完整的调用流程:

  1. 配置加载阶段: 读取 YAML/JSON 配置文件,得到观察者列表
  2. 工厂创建阶段: ObserverFactory 根据配置动态创建 Observer 实例
  3. 注册阶段: 将 Observer 注册到 EventManager(单例)中
  4. 运行阶段: 业务代码触发事件,EventManager 通知所有相关的 Observer

这个链路里,单例保证了全局唯一的事件中心,工厂保证了观察者的灵活创建。两者配合,天衣无缝。

SVG 流程图:三种模式整合后的观察者创建流程

工厂模式 + 单例模式 + 观察者模式 整合流程 配置文件 (YAML/JSON) ObserverFactory(工厂) 动态创建:EmailObserver / SmsObserver / PushObserver EventManager(单例) 配置驱动 → 工厂创建 → 注册到单例事件管理器

避坑指南:工厂创建观察者时的常见问题

我在实际项目中踩过几个坑,分享给你:

  • 观察者创建太频繁: 如果每次事件触发都 new 一个观察者,内存会暴涨。我建议在工厂里加缓存,或者让观察者实现 Disposable 接口,用完即销毁。
  • 构造参数不一致: 不同观察者的构造参数可能完全不同。EmailObserver 要邮箱地址,SmsObserver 要手机号。我的做法是统一用 Map<String, String> 传参,在工厂内部解析。
  • 工厂异常处理: 如果创建失败(比如配置错误),不要静默吞掉异常。我习惯在工厂里抛出自定义的 ObserverCreationException,上层可以统一处理。
总结一下: 工厂模式在这里扮演了“观察者生产线”的角色。它把创建逻辑从业务代码中抽离出来,让系统更容易扩展。配合单例的事件管理器,我们得到了一个灵活、高效、可配置的通知系统。

嗯,这一章就到这里。三种模式整合的威力,你已经看到了。下一章我们会继续深入,看看如何用策略模式进一步优化观察者的行为逻辑。


公众号:蓝海资料掘金营,微信deep3321