设计模式:观察者模式

设计模式:观察者模式

在开发一个电商系统时,用户下单成功后需要做三件事:扣减库存、发送短信通知、记录日志。最简单的实现方法是把这三步逻辑直接写在下单方法里:

java 复制代码
public void createOrder(Order order) {
    // 创建订单
    orderDao.save(order);
    // 扣减库存
    inventoryService.deduct(order.getProductId());
    // 发送短信
    smsService.send(order.getUserId(), "下单成功");
    // 记录日志
    logService.info("订单创建: " + order.getId());
}

功能是实现了,但问题是:如果以后还要加"发送邮件通知"、"更新用户积分"等等需求呢?每加一个需求,就得改一次 createOrder 方法。这个方法会越来越臃肿,而且下单逻辑和这些后续操作完全耦合在一起。

对于这个问题,我们就可以使用观察者模式来优化。

观察者模式是什么

观察者模式定义了一种一对多的依赖关系:当一个对象(被观察者)状态发生变化时,所有依赖它的对象(观察者)都会自动收到通知。

基本结构

观察者模式的核心角色只有两个:

在观察者模式中,创建订单是被观察者,扣减库存、发送短信等连带操作是观察者。每个观察者注册到被观察者上,当订单创建成功时,被观察者通知所有已注册的观察者,观察者收到通知后各自执行对应的操作。

实现步骤

第一步:定义观察者接口

所有观察者都要实现同一个接口,这样被观察者才能统一通知:

java 复制代码
public interface Observer {
    void update(String event, Object data);
}

update 方法接收两个参数:事件类型和事件数据,观察者根据这两个参数决定做什么。

第二步:定义被观察者(事件中心)

被观察者需要维护观察者列表、提供注册方法、状态变化时通知所有观察者:

java 复制代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EventCenter {
    private Map<String, List<Observer>> listeners = new HashMap<>();

    public void subscribe(String event, Observer observer) {
        if (!listeners.containsKey(event)) {
            listeners.put(event, new ArrayList<>());
        }
        listeners.get(event).add(observer);
    }

    public void unsubscribe(String event, Observer observer) {
        List<Observer> list = listeners.get(event);
        if (list != null) {
            list.remove(observer);
        }
    }

    public void publish(String event, Object data) {
        List<Observer> list = listeners.get(event);
        if (list != null) {
            for (Observer observer : list) {
                observer.update(event, data);
            }
        }
    }
}

如果事件还没有注册过观察者,就自动创建一个空列表,调用方不需要关心初始化问题。

第三步:实现具体观察者

java 复制代码
// 库存扣减观察者
public class InventoryObserver implements Observer {
    @Override
    public void update(String event, Object data) {
        Order order = (Order) data;
        System.out.println("扣减库存: 商品" + order.getProductId() + ", 数量" + order.getQuantity());
    }
}

// 短信通知观察者
public class SmsObserver implements Observer {
    @Override
    public void update(String event, Object data) {
        Order order = (Order) data;
        System.out.println("发送短信: 用户" + order.getUserId() + "下单成功");
    }
}

// 日志记录观察者
public class LogObserver implements Observer {
    @Override
    public void update(String event, Object data) {
        Order order = (Order) data;
        System.out.println("记录日志: 订单" + order.getId() + "已创建");
    }
}

每个观察者只关心自己的逻辑,不知道其他观察者的存在。

第四步:在业务中使用

java 复制代码
public class OrderService {
    private EventCenter eventCenter;

    public OrderService(EventCenter eventCenter) {
        this.eventCenter = eventCenter;
    }

    public void createOrder(Order order) {
        orderDao.save(order);
        eventCenter.publish("order.created", order);
    }
}

启动时注册观察者:

java 复制代码
EventCenter eventCenter = new EventCenter();
eventCenter.subscribe("order.created", new InventoryObserver());
eventCenter.subscribe("order.created", new SmsObserver());
eventCenter.subscribe("order.created", new LogObserver());

OrderService orderService = new OrderService(eventCenter);
orderService.createOrder(new Order("001", "P001", 2, "U123"));

输出:

复制代码
扣减库存: 商品P001, 数量2
发送短信: 用户U123下单成功
记录日志: 订单001已创建

完整执行流程

下单服务和后续操作完全解耦。需要加新功能时,写一个 Observer 实现类注册一下就行,不用改任何已有代码。

观察者模式 vs 直接调用

对比项 直接调用 观察者模式
耦合度 下单服务需要知道所有后续操作 下单服务只发布事件,不知道谁在监听
扩展性 新增操作需改下单服务代码 新增操作只需注册新观察者
可测试性 难以单独测试下单逻辑 可以单独测试下单逻辑,观察者独立测试
执行顺序 代码顺序固定 观察者执行顺序取决于注册顺序
复杂度 简单直接 引入了间接层,稍微复杂

消息队列:RabbitMQ、Kafka 这些消息中间件,本质上也是观察者模式的分布式实现。生产者发布消息,消费者订阅并处理。

小结

观察者模式的核心:发布-订阅,事件驱动。被观察者只管发布事件,观察者自己决定要不要监听、监听后做什么。两者通过事件连接,互不依赖。

这个模式解决的是"当A发生变化时,B、C、D需要做出反应"这类问题。如果代码里到处都是"做完A之后紧接着做B、C、D",而且B、C、D还在不断膨胀,就是观察者模式该出场的时候。