XiaoLin's Blog

Xiao Lin

观察者设计模式学习笔记

4
2024-02-26

观察者设计模式介绍

观察者设计模式(Observer Pattern)是一种对象行为模式,主要用于实现事件处理系统,又称为发布-订阅模式。在该模式中,一个目标对象管理所有依赖于它的观察者对象,并在其状态发生改变时主动发出通知。通常,这通过调用各观察者所提供的方法来实现。观察者模式将观察者和被观察的对象分离开,使得它们之间保持松散耦合。

观察者模式包含两个主要角色:观察者(Observer)和被观察对象(Subject)。观察者关注被观察对象的状态变化,并在状态发生变化时被通知。观察者模式适用于多个场景,例如当一个抽象模型有两个方面,其中一个方面依赖于另一方面时;当一个对象的状态改变需要同时改变其他对象,而不知道具体有多少对象需要被改变时;以及当一个对象必须通知其他对象,而它又不能假定其他对象是谁时。

观察者模式的主要优点是解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。然而,在应用观察者模式时,也需要考虑开发过程中的复杂性,例如程序中包括一个被观察者和多个被观察者时,开发和调试较为复杂。此外,Java 中的消息通知默认是顺序执行的,一个观察者的卡顿会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer 等等。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

观察者设计模式实现

让我们通过一个简单的例子来实现观察者模式。假设我们有一个气象站(WeatherStation),需要向许多不同的显示设备(如手机 App、网站、电子屏幕等)提供实时天气数据。

  • 定义观察者
package xyz.xiaolinz.demo.observer.calculator;  
  
/**  
 * 观察者  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */
 public interface Observer {  
  
    /**  
     * 更新  
     *  
     * @param temperature 温度  
     * @param humidity    湿度  
     * @param pressure    压力  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    
     void update(float temperature, float humidity, float pressure);  
  
}
  • 定义主题(Subject)
package xyz.xiaolinz.demo.observer.calculator;  
  
import java.util.Observer;  
  
/**  
 * 主题  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */
 public interface Subject {  
  
    /**  
     * 注册观察员  
     *  
     * @param o 哦  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    
     void registerObserver(Observer o);  
  
    /**  
     * 删除观察者  
     *  
     * @param o 哦  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    
     void removeObserver(Observer o);  
  
    /**  
     * 通知观察员  
     *  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    
     void notifyObservers();  
  
}
  • 定义具体的主题在我们这个例子里就是气象站
package xyz.xiaolinz.demo.observer.calculator;  
  
import java.util.ArrayList;  
import java.util.List;  
  
/**  
 * 气象站  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */
 public class WeatherStation implements Subject {  
  
    private final List<Observer> observers = new ArrayList<>();  
  
    // 温度  
    private float temperature;  
    // 湿度  
    private float humidity;  
    // 大气压  
    private float pressure;  
  
    @Override  
    public void registerObserver(Observer o) {  
        if (!observers.contains(o)) {  
            observers.add(o);  
        }  
    }  
  
    @Override  
    public void removeObserver(Observer o) {  
        observers.remove(o);  
    }  
  
    @Override  
    public void notifyObservers() {  
        observers.forEach(observer -> observer.update(temperature, humidity, pressure));  
    }  
  
    /**  
     * 设置测量值  
     *  
     * @param temperature 温度  
     * @param humidity    湿度  
     * @param pressure    压力  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    
     public void setMeasurements(float temperature, float humidity, float pressure) {  
        this.temperature = temperature;  
        this.humidity = humidity;  
        this.pressure = pressure;  
        // 测量值发生了变化  
        notifyObservers();  
    }  
  
}
  • 定义观察者具体实现
package xyz.xiaolinz.demo.observer.calculator;  
  
/**  
 * 电话应用程序  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 * @see Observer  
 */
 public class PhoneApp implements Observer {  
    @Override  
    public void update(float temperature, float humidity, float pressure) {  
        System.out.println("手机端收到了气象站的数据,温度:" + temperature + ",湿度:" + humidity + ",压力:" + pressure);  
    }  
}
  • 结果
package xyz.xiaolinz.demo.observer.calculator;  
  
/**  
 * 主要  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */public class Main {  
    public static void main(String[] args) {  
        final WeatherStation weatherStation = new WeatherStation();  
        final PhoneApp phoneApp = new PhoneApp();  
        weatherStation.registerObserver(phoneApp);  
        weatherStation.setMeasurements(1, 2, 3);  
    }  
}

image.png

观察者设计模式具体应用 - 发布订阅

发布-订阅模式观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处,但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性,都是用于实现对象间的松耦合通信。可以将发布-订阅模式看作是观察者模式的一种变体或扩展。

发布-订阅模式(生产者和消费者)与观察者模式类似,但它们之间有一个关键区别:发布-订阅模式引入了一个第三方组件(通常称为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除,从而提高了系统的灵活性和可扩展性。

Java 中的发布-订阅模式示例:

interface Subscriber {
    void onEvent(String event);
}

class ConcreteSubscriber implements Subscriber {
    @Override
    public void onEvent(String event) {
        System.out.println("收到事件: " + event);
    }
}

// 创建消息总线
class EventBus {
    // 使用一个map维护,消息类型和该消息的订阅者
    private Map<String, List<Subscriber>> subscribers = new HashMap<>();

    // 订阅一个消息
    public void subscribe(String eventType, Subscriber subscriber) {
        subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(subscriber);
    }

    // 接触订阅
    public void unsubscribe(String eventType, Subscriber subscriber) {
        List<Subscriber> subs = subscribers.get(eventType);
        if (subs != null) {
            subs.remove(subscriber);
        }
    }

    // 发布事件
    public void publish(String eventType, String event) {
        List<Subscriber> subs = subscribers.get(eventType);
        if (subs != null) {
            for (Subscriber subscriber : subs) {
                subscriber.onEvent(event);
            }
        }
    }
}

// 使用示例:
public class Main {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        Subscriber subscriber1 = new ConcreteSubscriber();
        Subscriber subscriber2 = new ConcreteSubscriber();
        // 订阅事件
        eventBus.subscribe("eventA", subscriber1);
        eventBus.subscribe("eventA", subscriber2);

        // 发布事件
        eventBus.publish("eventA", "这是事件A的内容");

        // 取消订阅
        eventBus.unsubscribe("eventA", subscriber1);

        // 再次发布事件
        eventBus.publish("eventA", "这是事件A的新内容");
    }
}

总结一下两者的区别:

  1. 通信方式:观察者模式中,观察者与被观察者之间存在直接的关联关系,而发布-订阅模式中,发布者和订阅者通过一个第三方组件(消息代理或事件总线)进行通信,彼此之间不存在直接关联关系。
  2. 系统复杂性:发布-订阅模式引入了一个额外的组件(消息代理或事件总线),增加了系统的复杂性,但同时也提高了系统的灵活性和可扩展性。
  3. 使用场景:观察者模式适用于需要将状态变化通知给其他对象的情况,而发布-订阅模式适用于事件驱动的系统,尤其是那些需要跨越多个模块或组件进行通信的场景。

发布-订阅模式和传统的观察者模式相比,在某些方面具有优势。以下是发布-订阅模式相对于观察者模式的一些优点:

  1. 解耦:在发布-订阅模式中,发布者和订阅者之间没有直接关联,它们通过一个中间组件(消息代理或事件总线)进行通信。这种间接通信可以使发布者和订阅者在运行时动态地添加或删除,从而进一步降低了它们之间的耦合度。
  2. 可扩展性:发布-订阅模式允许您更容易地向系统中添加新的发布者和订阅者,而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展性。
  3. 模块化:由于发布者和订阅者之间的通信通过中间组件进行,您可以将系统划分为更小、更独立的模块。这有助于提高代码的可维护性和可读性。
  4. 异步通信:发布-订阅模式通常支持异步消息传递,这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。
  5. 消息过滤:在发布-订阅模式中,可以利用中间组件对消息进行过滤,使得订阅者只接收到感兴趣的消息。这可以提高系统的性能,减少不必要的通信开销。

然而,发布-订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情况下,观察者模式可能更适合,而在其他情况下,发布-订阅模式可能是更好的选择。

模拟 Spring Event 的发布事件模式

Spring Event 是 Spring 框架中用于处理事件驱动编程的模块。它基于观察者模式,允许应用程序的不同组件之间以松耦合的方式通信。以下是 Spring Event 发布/监听机制的详细说明:

Spring Event 组件介绍

  1. ApplicationEvent

    • 这是一个接口,表示所有自定义事件的基础类。
    • 所有要发布的事件都需要扩展这个类。
  2. ApplicationListener

    • 这个接口代表了一个监听器,它会监听特定类型的 ApplicationEvent。
    • 当匹配的事件被发布时,它的 onApplicationEvent 方法会被调用。
  3. ApplicationEventPublisher

    • 这个接口定义了发布事件的方法。
    • 任何实现了此接口的 bean 都可以发布事件。
    • 在 Spring 中,ApplicationContext 类实现这个接口。
  4. SimpleApplicationEventMulticaster

    • 这是实际执行事件分发的类,它维护一个列表的监听器,当事件被发布时,遍历这些监听器并调用它们的 onApplicationEvent 方法。
  • 使用过程
  1. 创建自定义事件
    • 创建一个新的 Java 类,扩展 ApplicationEvent,并添加任何必要的属性和方法。
public class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
  1. 创建监听器
    • 创建一个新的 Java 类,实现 ApplicationListener 接口,并覆盖 onApplicationEvent 方法。
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println("Received custom event: " + event.getMessage());
    }
}
  1. 注入事件发布器
    • 如果你想在普通的 bean 中发布事件,你可以通过 @Autowired 注解将 ApplicationContext 注入到你的 bean 中,然后使用它来发布事件。
@Service
public class MyService {
    private final ApplicationContext context;

    @Autowired
    public MyService(ApplicationContext context) {
        this.context = context;
    }

    public void doSomething() {
        // ...
        context.publishEvent(new CustomEvent(this, "Hello from my service"));
    }
}

或者,你也可以让某个 bean 实现 ApplicationEventPublisherAware 接口,Spring 会在初始化该 bean 的时候自动注入 ApplicationEventPublisher。

  1. 发布事件
    • 在需要的地方调用 ApplicationEventPublisher 的 publishEvent 方法,传入你想要发布的事件对象。
context.publishEvent(new CustomEvent(this, "A custom message"));
  1. 接收事件
    • publishEvent 被调用时,所有注册为 CustomEvent 监听器的 bean(比如上面示例中的 CustomEventListener)都会收到这个事件,并调用它们的 onApplicationEvent 方法。
  • 示例代码

下面是一个完整的示例代码,展示了如何使用 Spring Event 来发布和监听自定义事件:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class Example {

    @Autowired
    private ApplicationContext context;

    public void publishEvent() {
        context.publishEvent(new CustomEvent(this, "This is a custom event"));
    }
}

class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

@Component
class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println("Received custom event: " + event.getMessage());
    }
}

在这个例子中,当你调用 Example#publishEvent 方法时,CustomEventListener 将接收到 CustomEvent 并打印出消息。

自定义的实现

  • 定义事件等概念
package xyz.xiaolinz.demo.observer.publish;  
  
import lombok.Data;  
  
/**  
 * 订户对象  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */@Data  
public abstract class SubscriberObject {  
  
    private final Object source;  
  
    private final long timestamp;  
  
    public SubscriberObject(Object source) {  
        if (source == null) {  
            throw new IllegalArgumentException("source must not be null");  
        }  
        this.source = source;  
        this.timestamp = System.currentTimeMillis();  
    }  
  
}
  • 定义事件监听
package xyz.xiaolinz.demo.observer.publish;  
  
/**  
 * 订阅者监听器  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */@FunctionalInterface  
public interface SubscriberListener<Event extends SubscriberObject> {  
  
    /**  
     * 执行事件  
     *  
     * @param event 事件  
     * @author huangmuhong  
     * @date 2023/12/01  
     * @since 1.0.0  
     */    void onEvent(Event event);  
  
}
  • 定义事件总线
package xyz.xiaolinz.demo.observer.publish;  
  
import java.lang.reflect.ParameterizedType;  
import org.springframework.util.LinkedMultiValueMap;  
  
/**  
 * 事件总线  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/01  
 */public class EventBus implements EventBusInterface {  
  
    private final LinkedMultiValueMap<Class<SubscriberObject>, SubscriberListener<?>> subscriberListenerMap =  
        new LinkedMultiValueMap<>();  
  
    @Override  
    public EventBusInterface register(SubscriberListener<?> subscriber) {  
        // 寻找泛型为 SubscriberObject 的class  
        final var genericInterfaces = subscriber.getClass().getGenericInterfaces();  
        for (var genericInterface : genericInterfaces) {  
            if (genericInterface instanceof Class) {  
                continue;  
            }  
            final var parameterizedType = (ParameterizedType)genericInterface;  
            final var actualTypeArguments = parameterizedType.getActualTypeArguments();  
            for (var actualTypeArgument : actualTypeArguments) {  
                if (actualTypeArgument instanceof Class) {  
                    final var subscriberObjectClass = (Class<SubscriberObject>)actualTypeArgument;  
                    subscriberListenerMap.add(subscriberObjectClass, subscriber);  
                }  
            }  
        }  
        return this;  
    }  
  
    @Override  
    public void publish(SubscriberObject event) {  
        final var subscriberListeners = subscriberListenerMap.get(event.getClass());  
        if (subscriberListeners == null) {  
            return;  
        }  
        for (var subscriberListener : subscriberListeners) {  
            final var subscriber = (SubscriberListener<SubscriberObject>)subscriberListener;  
            subscriber.onEvent(event);  
        }  
    }  
}
  • 具体使用
package xyz.xiaolinz.demo.observer.publish;  
  
import lombok.Getter;  
  
/**  
 * 应用程序订阅者对象  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/02  
 * @see SubscriberObject  
 */@Getter  
public class AppSubscriberObject extends SubscriberObject {  
  
    private final String name;  
  
    public AppSubscriberObject(Object source) {  
        super(source);  
        this.name = "AppSubscriberObject";  
    }  
}

package xyz.xiaolinz.demo.observer.publish;  
  
/**  
 * 应用程序订阅者侦听器  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2023/12/02  
 * @see SubscriberListener  
 */public class AppSubscriberListener implements SubscriberListener<AppSubscriberObject> {  
    @Override  
    public void onEvent(AppSubscriberObject appSubscriberObject) {  
        System.out.println("我的名字是:" + appSubscriberObject.getName() + ",我收到了消息");  
    }  
}


package xyz.xiaolinz.demo.observer.publish;  
  
/**  
 * @author huangmuhong  
 * @date 2023/12/2  
 */public class Main {  
    public static void main(String[] args) {  
        EventBusInterface eventBus = new EventBus();  
        eventBus.register(new AppSubscriberListener());  
        eventBus.publish(new AppSubscriberObject(new Object()));  
    }  
}
  • 结果
    image.png