松耦合的设计模式-观察者

观察者模式是一种对象行为型模式,定义对象间一对多的依赖关系,当一个对象(被观察者/主题)的状态发生改变时,所有依赖它的对象(观察者)都会收到通知并自动更新。

代码实现

比如说在订单业务中,当我们实现订单状态更新的时候,如下代码所示:

typescript 复制代码
public class OrderServiceHardCode {
​
    public void changeOrderStatus(String orderId,String oldStatus,String newStatus){
        System.out.println("===== 订单" + orderId + "状态更新:" + oldStatus + "→" + newStatus + " =====");
        SmsService.send(orderId, oldStatus, newStatus);
        StockService.update(orderId, oldStatus, newStatus);
        LogService.record(orderId, oldStatus, newStatus);
        PointService.send(orderId, oldStatus, newStatus);
    }
​
    public static void main(String[] args) {
        OrderServiceHardCode orderService = new OrderServiceHardCode();
        // 模拟订单状态变更
        orderService.changeOrderStatus("ORDER_123456", "待支付", "已支付");
    }
}
​
​
// 短信工具类
class SmsService {
    public static void send(String orderId, String oldStatus, String newStatus) {
        System.out.println("【短信通知】订单" + orderId + "状态从" + oldStatus + "变更为" + newStatus + ",已通知用户!");
    }
}
​
// 库存工具类
class StockService {
    public static void update(String orderId, String oldStatus, String newStatus) {
        if ("已支付".equals(newStatus)) {
            System.out.println("【库存更新】订单" + orderId + "已支付,扣减对应商品库存!");
        } else if ("已取消".equals(newStatus)) {
            System.out.println("【库存更新】订单" + orderId + "已取消,恢复对应商品库存!");
        }
    }
}
​
// 日志工具类
class LogService {
    public static void record(String orderId, String oldStatus, String newStatus) {
        System.out.println("【日志记录】订单" + orderId + "状态变更:" + oldStatus + "→" + newStatus + ",操作时间:" + new Date());
    }
}
​
// 积分工具类
class PointService {
    public static void send(String orderId, String oldStatus, String newStatus) {
        if ("已完成".equals(newStatus)) {
            System.out.println("【积分发放】订单" + orderId + "已完成,为用户发放100积分!");
        }
    }
}

思考一下上面的实现有什么问题。

观察工具类的调用方法,方法参数一样,可以统一可以作为一个接口。如果我们后续再加或者减去工具类,那么原有的changeOrderStatus方法就必须要改动。每次都是通过具体的工具类去调用方法,这是针对具体编程,不方便以后扩展。根据OO设计思想我们要

针对接口编程,不针对实现编程

订单类与工具类订单类存在依赖关系,且是一对多的依赖关系,订单要通知这些工具类改变状态,所以我们可以考虑将上面代码用观察者模式实现。

先把工具类的方法都定义Observer接口的方法,让工具类都实现这个接口的方法。这个接口就是观察者,这些工具类就是具体的观察者对象,短信,库存,日志,积分观察者。然后我们再定义一个Subject(主题)的接口,让具体对象实现主题接口的方法,这个类也叫被观察者。被观察者也就是文中的OrderService,内部维护一个存储容器,用来注册和移除观察者,并通过通知方法来通知所有的观察者。如下代码所示:

typescript 复制代码
public class OrderObserverTest {
​
    public static void main(String[] args) {
        // 1. 创建具体主题(订单服务)
        OrderService orderService = new OrderService();
​
        // 2. 注册所有观察者
        orderService.registerObserver(new SmsNotifyObserver());
        orderService.registerObserver(new StockUpdateObserver());
        orderService.registerObserver(new LogRecordObserver());
        orderService.registerObserver(new PointSendObserver());
​
        // 3. 模拟订单状态变更:待支付→已支付
        System.out.println("=== 测试1:订单待支付→已支付 ===");
        orderService.notifyObservers("ORDER_123456", "待支付", "已支付");
​
        System.out.println("\n=== 测试2:订单已支付→已完成 ===");
        // 4. 模拟订单状态变更:已支付→已完成
        orderService.notifyObservers("ORDER_123456", "已支付", "已完成");
​
        System.out.println("\n=== 测试3:移除短信观察者后,订单已完成→已取消 ===");
        // 5. 移除短信观察者(扩展能力)
        orderService.removeObserver(new SmsNotifyObserver());
        orderService.notifyObservers("ORDER_123456", "已完成", "已取消");
    }
}
​
//主题
interface OrderSubject {
    void registerObserver(OrderObserver observer);
    void removeObserver(OrderObserver observer);
    void notifyObservers(String orderId, String oldStatus, String newStatus);
}
​
class OrderService implements OrderSubject {
​
    private List<OrderObserver> observers = new ArrayList<>();
​
    @Override
    public void registerObserver(OrderObserver observer) {
        observers.add(observer);
    }
​
    @Override
    public void removeObserver(OrderObserver observer) {
        if (observer != null) {
            observers.remove(observer);
        }
    }
​
    @Override
    public void notifyObservers(String orderId, String oldStatus, String newStatus) {
        // 1. 先更新订单状态(实际业务中写入数据库)
        System.out.println("===== 订单" + orderId + "状态更新:" + oldStatus + "→" + newStatus + " =====");
        // 2. 通知所有观察者
        for (OrderObserver observer : observers) {
            observer.update(orderId, oldStatus, newStatus);
        }
    }
}
​
interface OrderObserver {
    void update(String orderId, String oldStatus, String newStatus);
}
​
class SmsNotifyObserver implements OrderObserver {
    @Override
    public void update(String orderId, String oldStatus, String newStatus) {
        System.out.println("【短信通知】订单" + orderId + "状态从" + oldStatus + "变更为" + newStatus + ",已通知用户!");
        // 实际业务中:调用短信网关API发送短信
    }
}
​
class StockUpdateObserver implements OrderObserver {
    @Override
    public void update(String orderId, String oldStatus, String newStatus) {
        if ("已支付".equals(newStatus)) {
            System.out.println("【库存更新】订单" + orderId + "已支付,扣减对应商品库存!");
        } else if ("已取消".equals(newStatus)) {
            System.out.println("【库存更新】订单" + orderId + "已取消,恢复对应商品库存!");
        }
        // 实际业务中:调用库存服务更新库存
    }
}
​
class LogRecordObserver implements OrderObserver {
    @Override
    public void update(String orderId, String oldStatus, String newStatus) {
        System.out.println("【日志记录】订单" + orderId + "状态变更:" + oldStatus + "→" + newStatus + ",操作时间:" + System.currentTimeMillis());
        // 实际业务中:写入数据库/日志文件
    }
}
​
class PointSendObserver implements OrderObserver {
    @Override
    public void update(String orderId, String oldStatus, String newStatus) {
        if ("已完成".equals(newStatus)) {
            System.out.println("【积分发放】订单" + orderId + "已完成,为用户发放100积分!");
        }
        // 实际业务中:调用积分服务发放积分
    }
}

通过观察者实现代码以后,会发现订单类(主题)和工具类(观察者)实现了松耦合。订单类并不关心工具类的实现,工具类也不知道订单类的代码细节,但它们依旧可以交互。改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,只要它们遵守接口,我们就可以自由的改变。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象互相依赖降到了最低。

观察者模式提供了一种对象设计,让主题与观察者之间松耦合

核心结构(UML类图)
  • Subject(主题)

    • 提供注册、删除和通知观察者对象的接口
    • 每个主题可以有多个观察者
  • ConcreteSubject(具体主题)

    • 具体主题总是实现主题接口
    • 获取和设置状态的方法
    • Notify方法用于在状态改变时更新所有观察者
  • Observer(观察者)

    • 给观察者对象定义更新方法的接口
  • ConcreteObserver(具体观察者)

    • 具体观察者实现接口更新方法
    • 存储状态,这些状态和主题状态保持一致

当主题发生状态变更的时候,会通知到注册在主题的观察者。

框架中的应用

Spring的事件驱动模型(Spring Event) 是对观察者模式标准化实现,解耦事件发布者和监听者,参考代码所示:

csharp 复制代码
public class SpringEventTest {
​
    public static void main(String[] args) {
        EventPublisher publisher = new EventPublisher();
​
        // 2. 注册监听器(观察者)
        publisher.addListener(new LogListener());
        publisher.addListener(new InitListener());
​
        // 3. 发布应用启动事件(状态变更)
        System.out.println("=== 发布应用启动事件 ===");
        publisher.publishEvent(new AppStartedEvent(publisher, "电商后台系统"));
​
        // 4. 移除日志监听器,再次发布事件
        System.out.println("\n=== 移除日志监听器后,发布事件 ===");
        publisher.removeListener(new LogListener());
        publisher.publishEvent(new AppStartedEvent(publisher, "用户中心系统"));
    }
}
abstract class ApplicationEvent{
    private final Object source;
    private final long timestamp;
​
    public ApplicationEvent(Object source) {
        this.source = source;
        this.timestamp = System.currentTimeMillis();
    }
​
    // getter
    public Object getSource() { return source; }
    public long getTimestamp() { return timestamp; }
}
​
//具体的状态
class AppStartedEvent extends ApplicationEvent {
​
    private final String appName;
​
    public AppStartedEvent(Object source, String appName) {
        super(source);
        this.appName = appName;
    }
​
    public String getAppName() {
        return appName;
    }
}
​
interface ApplicationListener<E extends ApplicationEvent> {
​
    // 监听器方法
    void onApplicationEvent(E event);
}
​
class LogListener implements ApplicationListener<AppStartedEvent> {
​
    @Override
    public void onApplicationEvent(AppStartedEvent event) {
        System.out.println("【日志监听器】应用" + event.getAppName() + "于" + event.getTimestamp() + "启动完成!");
    }
}
​
class InitListener implements ApplicationListener<AppStartedEvent> {
​
    @Override
    public void onApplicationEvent(AppStartedEvent event) {
        System.out.println("【初始化监听器】应用" + event.getAppName() + "启动完成,开始加载缓存数据...");
        // 模拟初始化缓存
        System.out.println("【初始化监听器】缓存加载完成!");
    }
}
​
class EventPublisher {
    private final List<ApplicationListener> listeners=new ArrayList<>();
​
    public void addListener(ApplicationListener listener) {
        listeners.add(listener);
    }
​
    public void removeListener(ApplicationListener listener) {
        listeners.remove(listener);
    }
​
    public void publishEvent(ApplicationEvent event) {
        for (ApplicationListener listener : listeners) {
            if (listener instanceof ApplicationListener) {
                ((ApplicationListener) listener).onApplicationEvent(event);
            }
        }
    }
}
总结

把观察者模式类比现实生活中的报纸。出版社相当于主题,订阅报纸的人相当于观察者。出版社一旦发布新闻报纸,订阅报纸的人都能收到。如果系统中有对象间存在依赖关系,并且需要推送给依赖对象状态的场景,试着用用观察者模式吧!

相关推荐
鱼跃鹰飞2 小时前
怎么排查线上CPU100%的问题
java·jvm·后端
哈库纳2 小时前
dbVisitor 的双层适配架构
后端
我想问问天2 小时前
【从0到1大模型应用开发实战】04|RAG混合检索
后端·aigc
Andy工程师2 小时前
不要在 Bean(尤其是单例 Bean)里积累大量数据
后端
想用offer打牌2 小时前
Google Code Wiki: AI 代码知识库
后端·程序员·架构
ZoeGranger2 小时前
【Spring】使用Spring实现AOP
后端
李日灐3 小时前
C++STL:仿函数、模板(进阶) 详解!!:“伪装术”和模板特化、偏特化的深度玩法指南
开发语言·c++·后端·stl
何中应3 小时前
使用Spring自带的缓存注解维护数据一致性
java·数据库·spring boot·后端·spring·缓存