观察者模式学习

观察者模式(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、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

从定义中可以知道,观察者主要应用于通知的场景,并且通知的对象可能需要频繁的改变。它可以解耦被观察者与观察者。

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。

经典实现方式

我们先来看其中最经典的一种实现方式。这也是在讲到这种模式的时候,很多书籍或资料给出的最常见的实现方式。具体的代码如下所示:

java 复制代码
public interface Subject {
  void registerObserver(Observer observer);
  void removeObserver(Observer observer);
  void notifyObservers(Message message);
}

public interface Observer {
  void update(Message message);
}

public class ConcreteSubject implements Subject {
  private List<Observer> observers = new ArrayList<Observer>();

  @Override
  public void registerObserver(Observer observer) {
    observers.add(observer);
  }

  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }

  @Override
  public void notifyObservers(Message message) {
    for (Observer observer : observers) {
      observer.update(message);
    }
  }

}

public class ConcreteObserverOne implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverOne is notified.");
  }
}

public class ConcreteObserverTwo implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverTwo is notified.");
  }
}

public class Demo {
  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.registerObserver(new ConcreteObserverOne());
    subject.registerObserver(new ConcreteObserverTwo());
    subject.notifyObservers(new Message());
  }
}

这个例子很好理解:

它具有一个被观察者接口(Subject)和一个观察者接口(Observer);

所有具体的被观察者都需要实现Subject接口,即所有的被观察者都具有注册观察者方法(registerObserver)、删除观察者方法(removeObserver)、通知观察者方法(notifyObservers);

所有具体的观察者都需要实现Observer接口,即所有的观察者都具有执行通知逻辑的方法(update);

在被观察者中通过一个List来保存所有注册的观察者,在需要时通过遍历List来通知所有的观察者。

但上面的代码算是观察者模式的"模板代码",只能反映大体的设计思路。在真实的软件开发中,并不需要照搬上面的模板代码。而且Java中也有一些第三方框架供我们使用,它们提供了实现观察者模式的骨架代码,我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发,如Guava 的EventBus、Spring Event等。

观察者模式的实现原理分析

如何通知观察者?

我们看到,被观察者需要通知每一个观察者,所以上面示例中被观察者通过一个List来保存了观察者,然后通过遍历List来实现通知每一个观察者。当然通过其他的容器也可以实现,比如Guava的EventBus就是用ConcurrentMap来存储的观察者信息的。

还有被观察者中保存的是观察者的接口,并不知道观察者的细节,即被观察者和观察者之间用松耦合方式结合(loosecoupling),符合"为交互对象之间的松耦合设计而努力"的设计原则。

在上面示例中是被观察者主动将消息推送到观察者中的,这种方式被称为推(push)。还有一种称为拉(pull)的方式,即被观察者通知观察者后,观察者再从被观察者中获取所需的数据。pull方式的前提条件是,被观察者需要提供对外获取数据的接口,同时观察者中需要能获取到被观察者对象。显然,pull方式使被观察者与观察者联系更紧密了,这不一定是一个好的现象

如何更具有复用性?

被观察者需要提供注册、通知等通用接口,其实这些操作和我们实际业务关系不大,可以考虑抽取出来。此时有两种可以考虑的方式,

一种是继承方式,将通用的操作都抽取到父类中,所有的被观察者只需要继承父类就可以具有注册、通知等操作的实现,JDK1.0中的观察者模式就是这样做的,它提供了Observable类用于给被观察者继承。

一种是组合方式,将通用的操作都抽取到其他类中,被观察者通过委托该类来实现注册、通知等通用操作。如果使用Guava的EventBus,就是通过组合EventBus类来实现的。

我们都知道组合的方式优于继承,因为继承有诸多限制,比如单继承。所以一般都推荐使用组合的方式,目前常用的第三方框架采用的都是组合的方式。

同步通知与异步通知

在通知观察者的方式上我们可以有两种方式,同步阻塞的实现方式和异步非阻塞的实现方式。

同步阻塞的实现方式

观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。

上面讲到的用户注册的例子就是同步阻塞方式,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。

异步非阻塞的实现方式

如果注册接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。

实现异步非阻塞的方式我们既可以自己手动创建线程来实现,也可以采用第三方框架提供的异步执行功能,如如Guava 的EventBus、Spring Event等。

跨进程间的调用

我们一般所介绍的观察者模式都是指在一个进程间(通常就是指同一个项目代码)进行的通知,但其实观察者模式也可以应用于进程间,比如如果用户注册成功之后,我们需要发送用户信息给大数据征信系统,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的。

要实现跨进程的观察者模式,其实就是要实现进程间的通讯,这个我们就比较熟悉了,常用的就是:

RPC 接口调用的方式。

消息队列的方式。(采用消息队列的发布-订阅模式)

消息队列方式相对于RPC方式来说,被观察者和观察者解耦更加彻底,两部分的耦合更小,因为被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。不过弊端就是需要引入一个新的系统(消息队列),增加了维护成本。

相关推荐
霁月风1 分钟前
设计模式——观察者模式
c++·观察者模式·设计模式
hong16168833 分钟前
跨模态对齐与跨领域学习
学习
阿伟来咯~1 小时前
记录学习react的一些内容
javascript·学习·react.js
Suckerbin2 小时前
Hms?: 1渗透测试
学习·安全·网络安全
水豚AI课代表2 小时前
分析报告、调研报告、工作方案等的提示词
大数据·人工智能·学习·chatgpt·aigc
聪明的墨菲特i2 小时前
Python爬虫学习
爬虫·python·学习
Diamond技术流2 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
斑布斑布2 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
Chef_Chen3 小时前
从0开始学习机器学习--Day13--神经网络如何处理复杂非线性函数
神经网络·学习·机器学习
lulu_gh_yu3 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法