观察者模式——对象一对多通信的实现


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新

现实世界的模型:

  • 订阅周刊,每次周刊发布会发送给用户。
  • 高考的时候,学生收到结束广播要停笔,教师要收卷。

根据业务模型的不同,有这几种类型的观察者:

  • 发布-订阅模式(Publish/Subscribe)。
  • 模型-视图模式(Model/View)。
  • 源-监听模式(Source/Listener)。

发布-订阅模式,订阅者会订阅一些主题,在主题发生变化的时候会收到通知。

模型-视图模式,常见于 MVC、MVP 或 MVVM 架构中,特点为 View 和 Model 进行绑定,在 Model 变化的时候 View 也同步变化。

源-监听模式,常见 UI 层的交互事件的监听。比如在前端交互场景下的单击、双击、滑动、触摸、长按等等,通过设置一个或者多个监听来对事件响应。也可以对一些执行任务加入回调方法,在执行到某个阶段抛出方法。

一对多通信

2. 设计

观察者模式主要有这几个角色:

  • 抽象主题(Subject)。被观察的对象,维护观察者集合,定义了注册和注销观察者的方法,定义了通知方法。主题可以根据业务来决定是接口、抽象类还是具体类。
  • 具体主题(Concrete Subject)。在主题类需要扩展的情况下,会有具体主题类。比如内置状态,当观察者观察的状态改变时发出通知。
  • 抽象观察者(Observer)。声明了被主题调用的方法。
  • 具体观察者(Concrete Observer)。实现了被主题调用的方法。

观察者模式-类图

设计抽象主题类,内部维护一个观察者列表,同时提供三个基本操作:注册、注销、通知。

public class Subject {

    private final List<IObserver> list = new ArrayList<IObserver>();

    public void register(IObserver observer) {
        list.add(observer);
    }

    public void unregister(IObserver observer) {
        list.remove(observer);
    }

    public void notifyObservers() {
        for (IObserver observer : list) {
            observer.update();
        }
    }
}

具体主题类,提供接口供使用者调用,然后调用抽象主题的通知方法把变化通知给观察者们。复杂一些的主题类还会有状态等条件。

public class ConcreteSubject extends Subject {

    public void change() {
        notifyObservers();
    }
}

抽象观察者,定义接收通知的接口:

public interface IObserver {
    void update();
}

具体观察者,实现接收通知的接口:

public class ObserverA implements IObserver {
    public void update() {
        System.out.println("A 收到通知");
    }
}

使用的时候,往主题里动态注册观察者。然后使用者直接调用主题的方法通知所有观察的观察者。

public class TestObserver {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ObserverA a = new ObserverA();
        ObserverB b = new ObserverB();
        ObserverC c = new ObserverC();

		// 发通知
        subject.change();

        // A 注册
        subject.register(a);
        subject.register(b);
        
        // 发通知
        subject.change();

        // A 注销,C 加入
        subject.unregister(a);
        subject.register(c);
        
        // 发通知
        subject.change();
    }
}

3. 应用

观察者的应用频率很高。很多框架或业务设计都可以看到它的影子。可以从几个维度思考是否需要使用观察者模式:

  • 是否有一对多通信的需求(当然一对一也是可以的)。
  • 是否有监听某种状态或者事件的需求。
  • 是否需要对系统不同组件进行解耦,不直接依赖,通过广播来传递通知。

3.1. 事件监听

这里举 Spring 容器事件监听的例子。

使用 Spring 时,如果需要监听 ApplicationContext 生命周期的事件,并且做出相应处理,会用到两个重要的类:

  • ApplicationListener,观察者,接收事件。
  • ApplicationEvent,对容器生命周期事件的封装。比如
    • ContextRefreshedEvent
    • ContextStartedEvent
    • ContextStoppedEvent
    • ContextClosedEvent
    • RequestHandledEvent

Spring 使用的是观察者模式实现外部对事件监听的。

ApplicationListener 就是观察者,定义如下:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);
}

接受事件通知的方法为 onApplicationEvent

主题应该是容器,但 Spring 这里做了进一步的抽象,观察者集合交给了 ApplicationEventMulticaster 来维护。所以这里的主题转为 ApplicationEventMulticaster,为一个广播发送者。

ApplicationEventMulticaster 的定义如下:

public interface ApplicationEventMulticaster {

	/**
	 * Add a listener to be notified of all events.
	 * @param listener the listener to add
	 */
	void addApplicationListener(ApplicationListener<?> listener);

	/**
	 * Add a listener bean to be notified of all events.
	 * @param listenerBeanName the name of the listener bean to add
	 */
	void addApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove a listener from the notification list.
	 * @param listener the listener to remove
	 */
	void removeApplicationListener(ApplicationListener<?> listener);

	/**
	 * Remove a listener bean from the notification list.
	 * @param listenerBeanName the name of the listener bean to add
	 */
	void removeApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove all listeners registered with this multicaster.
	 * <p>After a remove call, the multicaster will perform no action
	 * on event notification until new listeners are being registered.
	 */
	void removeAllListeners();

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
	 * if possible as it provides a better support for generics-based events.
	 * @param event the event to multicast
	 */
	void multicastEvent(ApplicationEvent event);

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>If the {@code eventType} is {@code null}, a default type is built
	 * based on the {@code event} instance.
	 * @param event the event to multicast
	 * @param eventType the type of event (can be null)
	 */
	void multicastEvent(ApplicationEvent event, ResolvableType eventType);

}

可以看到接口主要分三种,添加监听,删除监听和向注册的监听广播事件。

ApplicationEventMulticaster 的实现类为 SimpleApplicationEventMulticaster。

multicastEvent 会轮询注册的监听,传递事件。

容器 AbstractApplicationContext 会调用 publishEvent 调用 ApplicationEventMulticaster 去通知消息。

比如容器刷新结束时,在 AbstractApplicationContext#finishRefresh:

protected void finishRefresh() {

	...
	
	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));
	
	...
}

3.2. 事件总线

事件总线一般用在进行组件间的通信和解耦。是应用的事件发布/订阅的轻量级框架。可以实现多主题与多观察者,也就是多对多通信。

使用了观察者模式,在抛出消息后,会轮询观察者列表找到注册该事件的组件,更新通知。

Android 客户端的消息总线框架有 EventBus、Otto 等。各个组件(Activity 或 Fragment)通过 EventBus 进行解耦,只需要关心事件的接收和处理。

Google 的 Guava 包也有 EventBus,可以用来实现简单的事件总线。

图片来源:https://github.com/greenrobot/EventBus

3.3. 消息队列

消息队列中间件一般用来进行解耦、异步、削峰,分布式环境下生产者消费者模型的实现。

常见的 MQ 有 ActiveMQ、RocketMQ、Kafka 等。

使用了观察者模式中的发布-订阅模型。

消息队列增加了队列来储存消息。

Kafka

3.4. Reactive

响应式编程官网传送门:reactivex.io

Reactive

Reactive 响应式编程,事件驱动。

这里以 RxJava 框架举例。GitHub: ReactiveX/RxJava

RxJava 是一个支持响应式编程的 Java 框架。可以用来实现函数式编程、响应式编程、链式编程。

RxJava 底层就是用观察者模式实现的,可以认为是观察者的一大扩展。

创建被观察者:

// 创建被观察者
Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
	public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
		// 发射器,发送事件
       	emitter.onNext(1);
		emitter.onNext(2);
		emitter.onNext(3);
		emitter.onNext(4);
		emitter.onComplete();
	}
});

创建观察者:

// 创建观察者
Observer<Integer> observerA = new Observer<Integer>() {

	private Disposable disposable;

	public void onSubscribe(Disposable d) {
		this.disposable = d;
		System.out.println("A 订阅主题");
	}

	public void onNext(Integer integer) {
		System.out.println("A 收到 " + integer);
	}

	public void onError(Throwable e) {
		System.out.println("A 收到异常");
	}

	public void onComplete() {
		System.out.println("A 结束");

		// 取消订阅
		disposable.dispose();
	}
};

订阅:

observable.subscribe(observerA);

运行的输出:

A 订阅主题
A 收到 1
A 收到 2
A 收到 3
A 收到 4
A 结束

4. 特点

4.1. 优势

  • 系统解耦:被观察主题和观察者之间建立一个抽象耦合,主题无需了解具体观察者。新增主题类的话,如果两者没有强关联的情况下很方便。
  • 一对多通信:有一对多通信的场景,可以通过主题类广播通知。
  • 易于扩展:增加新的观察者无需修改原有代码。

4.2. 缺点

  • 观察者膨胀:数量过多的观察者,整个通知过程会有明显耗时。

  • 循环依赖:观察者和被观察主题存在循环依赖,会引起循环通知,轻则发出多个无效通知,重则系统崩溃。

    编写代码时候要提高警惕,如果有迫不得已的循环依赖,要加入明确退出条件避免循环通知。

4.3. 注意事项

  • 观察者和被观察主题之间要注意对循环依赖的处理。
发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90314237