观察者设计模式介绍
观察者设计模式(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);
}
}
观察者设计模式具体应用 - 发布订阅
发布 - 订阅模式和观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处,但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性,都是用于实现对象间的松耦合通信。可以将发布 - 订阅模式看作是观察者模式的一种变体或扩展。
发布 - 订阅模式(生产者和消费者)与观察者模式类似,但它们之间有一个关键区别:发布 - 订阅模式引入了一个第三方组件(通常称为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除,从而提高了系统的灵活性和可扩展性。
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的新内容");
}
}
总结一下两者的区别:
- 通信方式:观察者模式中,观察者与被观察者之间存在直接的关联关系,而发布 - 订阅模式中,发布者和订阅者通过一个第三方组件(消息代理或事件总线)进行通信,彼此之间不存在直接关联关系。
- 系统复杂性:发布 - 订阅模式引入了一个额外的组件(消息代理或事件总线),增加了系统的复杂性,但同时也提高了系统的灵活性和可扩展性。
- 使用场景:观察者模式适用于需要将状态变化通知给其他对象的情况,而发布 - 订阅模式适用于事件驱动的系统,尤其是那些需要跨越多个模块或组件进行通信的场景。
发布 - 订阅模式和传统的观察者模式相比,在某些方面具有优势。以下是发布 - 订阅模式相对于观察者模式的一些优点:
- 解耦:在发布 - 订阅模式中,发布者和订阅者之间没有直接关联,它们通过一个中间组件(消息代理或事件总线)进行通信。这种间接通信可以使发布者和订阅者在运行时动态地添加或删除,从而进一步降低了它们之间的耦合度。
- 可扩展性:发布 - 订阅模式允许您更容易地向系统中添加新的发布者和订阅者,而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展性。
- 模块化:由于发布者和订阅者之间的通信通过中间组件进行,您可以将系统划分为更小、更独立的模块。这有助于提高代码的可维护性和可读性。
- 异步通信:发布 - 订阅模式通常支持异步消息传递,这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。
- 消息过滤:在发布 - 订阅模式中,可以利用中间组件对消息进行过滤,使得订阅者只接收到感兴趣的消息。这可以提高系统的性能,减少不必要的通信开销。
然而,发布 - 订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情况下,观察者模式可能更适合,而在其他情况下,发布 - 订阅模式可能是更好的选择。
模拟 Spring Event 的发布事件模式
Spring Event 是 Spring 框架中用于处理事件驱动编程的模块。它基于观察者模式,允许应用程序的不同组件之间以松耦合的方式通信。以下是 Spring Event 发布 / 监听机制的详细说明:
Spring Event 组件介绍
-
ApplicationEvent:
- 这是一个接口,表示所有自定义事件的基础类。
- 所有要发布的事件都需要扩展这个类。
-
ApplicationListener:
- 这个接口代表了一个监听器,它会监听特定类型的 ApplicationEvent。
- 当匹配的事件被发布时,它的
onApplicationEvent
方法会被调用。
-
ApplicationEventPublisher:
- 这个接口定义了发布事件的方法。
- 任何实现了此接口的 bean 都可以发布事件。
- 在 Spring 中,ApplicationContext 类实现这个接口。
-
SimpleApplicationEventMulticaster:
- 这是实际执行事件分发的类,它维护一个列表的监听器,当事件被发布时,遍历这些监听器并调用它们的
onApplicationEvent
方法。
- 这是实际执行事件分发的类,它维护一个列表的监听器,当事件被发布时,遍历这些监听器并调用它们的
- 使用过程
- 创建自定义事件:
- 创建一个新的 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;
}
}
- 创建监听器:
- 创建一个新的 Java 类,实现 ApplicationListener 接口,并覆盖
onApplicationEvent
方法。
- 创建一个新的 Java 类,实现 ApplicationListener 接口,并覆盖
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println("Received custom event: " + event.getMessage());
}
}
- 注入事件发布器:
- 如果你想在普通的 bean 中发布事件,你可以通过
@Autowired
注解将 ApplicationContext 注入到你的 bean 中,然后使用它来发布事件。
- 如果你想在普通的 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。
- 发布事件:
- 在需要的地方调用 ApplicationEventPublisher 的
publishEvent
方法,传入你想要发布的事件对象。
- 在需要的地方调用 ApplicationEventPublisher 的
context.publishEvent(new CustomEvent(this, "A custom message"));
- 接收事件:
- 当
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()));
}
}
- 结果
评论区