24、三种模式整合(三):观察者模式监听工厂生产事件
好,我们继续往前走。
前面两章,我们把单例工厂搭好了,产品也能按需生成了。但有个问题——生产完了,然后呢?
在实际项目中,产品生产出来往往不是终点。比如订单系统里,商品入库后要通知库存模块更新、通知物流模块准备发货、通知财务模块记账……这些动作如果都写在工厂的createProduct()方法里,那工厂就变成了一锅粥。
我记得有一次接手一个老项目,工厂方法里塞了七八个业务回调,每次新增一个通知方,就得改工厂代码。改一次,测试回归一次,烦得很。后来我重构时引入了观察者模式,世界清净了。
观察者模式:让工厂学会“广播”
观察者模式的核心思想很简单:当一个对象状态发生变化时,自动通知所有依赖它的对象。
说白了,就是“发布-订阅”。工厂只管生产,生产完了喊一嗓子:“产品出来了!”谁想听,谁就自己注册一下。
核心角色:
- Subject(主题):工厂,负责维护观察者列表,生产时通知
- Observer(观察者):各个业务模块,收到通知后执行自己的逻辑
- Event(事件):生产事件,携带产品信息
先定义事件和观察者接口
我习惯先定义事件类,把生产出来的产品信息封装好。
// 生产事件
public class ProductionEvent {
private final String productType;
private final String productId;
private final long timestamp;
public ProductionEvent(String productType, String productId) {
this.productType = productType;
this.productId = productId;
this.timestamp = System.currentTimeMillis();
}
// getters...
}
观察者接口也很简单,就一个方法:
public interface ProductionObserver {
void onProductCreated(ProductionEvent event);
}
你想想看,这样设计的好处是什么?解耦。工厂不需要知道谁在监听,观察者也不需要知道工厂内部怎么生产的。双方只通过ProductionEvent这个契约沟通。
改造工厂:加入观察者管理
现在我们把单例工厂改造一下,让它支持观察者注册和通知。
public class ProductFactory {
private static volatile ProductFactory instance;
private final Map<String, Product> productCache = new ConcurrentHashMap<>();
private final List<ProductionObserver> observers = new CopyOnWriteArrayList<>();
private ProductFactory() {}
public static ProductFactory getInstance() {
if (instance == null) {
synchronized (ProductFactory.class) {
if (instance == null) {
instance = new ProductFactory();
}
}
}
return instance;
}
// 注册观察者
public void addObserver(ProductionObserver observer) {
observers.add(observer);
}
// 移除观察者
public void removeObserver(ProductionObserver observer) {
observers.remove(observer);
}
// 生产产品(核心方法)
public Product createProduct(String type, String id) {
Product product = null;
switch (type) {
case "A":
product = new ConcreteProductA(id);
break;
case "B":
product = new ConcreteProductB(id);
break;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
productCache.put(id, product);
// 通知所有观察者
ProductionEvent event = new ProductionEvent(type, id);
notifyObservers(event);
return product;
}
private void notifyObservers(ProductionEvent event) {
for (ProductionObserver observer : observers) {
observer.onProductCreated(event);
}
}
}
小提示:这里我用的是CopyOnWriteArrayList,而不是普通的ArrayList。为什么?因为观察者列表可能被多个线程同时读写,CopyOnWriteArrayList在遍历时不会抛ConcurrentModificationException。嗯,多线程环境下的坑,我踩过不少。
实现具体的观察者
假设我们有三个业务模块需要监听生产事件:
// 库存观察者
public class InventoryObserver implements ProductionObserver {
@Override
public void onProductCreated(ProductionEvent event) {
System.out.println("[库存] 产品 " + event.getProductId() + " 已入库,更新库存数量");
// 实际业务:更新数据库库存表
}
}
// 物流观察者
public class LogisticsObserver implements ProductionObserver {
@Override
public void onProductCreated(ProductionEvent event) {
System.out.println("[物流] 产品 " + event.getProductId() + " 待发货,生成运单");
// 实际业务:调用物流API
}
}
// 财务观察者
public class FinanceObserver implements ProductionObserver {
@Override
public void onProductCreated(ProductionEvent event) {
System.out.println("[财务] 产品 " + event.getProductId() + " 已生产,记录成本");
// 实际业务:生成财务凭证
}
}
组装起来:客户端怎么用
public class Application {
public static void main(String[] args) {
ProductFactory factory = ProductFactory.getInstance();
// 注册观察者
factory.addObserver(new InventoryObserver());
factory.addObserver(new LogisticsObserver());
factory.addObserver(new FinanceObserver());
// 生产产品——观察者自动收到通知
factory.createProduct("A", "P001");
factory.createProduct("B", "P002");
}
}
运行结果:
[库存] 产品 P001 已入库,更新库存数量
[物流] 产品 P001 待发货,生成运单
[财务] 产品 P001 已生产,记录成本
[库存] 产品 P002 已入库,更新库存数量
[物流] 产品 P002 待发货,生成运单
[财务] 产品 P002 已生产,记录成本
你看,工厂每生产一个产品,三个观察者自动执行各自的逻辑。如果以后要加一个“质检观察者”,只需要新增一个类实现ProductionObserver,然后注册进去就行。工厂代码一行都不用改。
三种模式整合后的架构图
下面这张图展示了单例、工厂、观察者三种模式如何协同工作:
避坑指南
我曾经踩过的坑:
- 观察者注册时机:如果在工厂初始化之前就注册观察者,没问题。但如果在生产过程中动态注册,要注意线程安全问题。我建议所有观察者在系统启动阶段统一注册。
- 观察者执行顺序:默认情况下,观察者按注册顺序执行。如果某个观察者执行耗时操作(比如调用外部API),会阻塞其他观察者。我一般会引入异步通知机制,或者用线程池来执行。
- 内存泄漏:观察者注册后如果不及时移除,会导致工厂持有观察者的引用,垃圾回收无法回收。特别是在Web应用中,每次请求都注册观察者的话,内存会慢慢涨上去。
什么时候该用这种整合?
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 订单创建后需要通知多个系统 | ✅ 强烈推荐 | 解耦订单服务和各个下游系统 |
| 数据导入完成后触发多个处理流程 | ✅ 推荐 | 避免导入逻辑和业务处理耦合 |
| 只有1-2个通知方,且不会变化 | ⚠️ 可考虑简化 | 直接调用可能更简单 |
| 通知方数量动态变化 | ✅ 必须使用 | 观察者模式天然支持动态增删 |
我个人习惯是:只要生产事件有超过一个消费者,我就用观察者模式。哪怕现在只有两个,谁知道三个月后会不会变成五个?
好,这一章就到这里。三种模式整合的核心思路你已经掌握了:单例保证工厂唯一,工厂负责产品创建,观察者负责后续处理。各司其职,互不干扰。
公众号:蓝海资料掘金营,微信deep3321