观察者模式也被称为发布订阅模式,或者叫监听器模式大家更不觉得陌生,是11种行为型模式之一。
观察者模式在平时运用的较多叫法也不一,常见的有观察者(Observer)、监听器(Listener)、订阅者(Subscriber)、消费者(Consumer)等,有观察者就会有被观察者,与观察者对应依次为主题(Subject)、分发器(Dispatcher),发布者(Publisher)、生产商(Producer)。
在 GoF 的《设计模式》一书中,它的定义是这样的:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。也就是说在被观察者与观察者之间建立依赖关系,观察者可能会有多个,当被观察者有变化时,会通知所有观察者。
观察者模式经典的实现
interface Observer {
public void handle(String massage);
}
interface Subject {
public void register(Observer observer);
public void remove(Observer observer);
public void notify(String message);
}
class TyphoonWarningSubject implements Subject {
List<Observer> observers = new ArrayList<>();
public void register(Observer observer) {
this.observers.add(observer);
}
public void remove(Observer observer) {
this.observers.remove(observer);
}
public void notify(String message) {
for (Observer observer : observers) {
observer.handle(message);
}
}
}
class SmsObserver implements Observer {
public void handle(String massage) {
//发布短信通知
}
}
class EmailObserver implements Observer {
public void handle(String massage) {
//发布邮件通知
}
}
public class Demo {
public static void main(String[] args) {
Subject subject = new TyphoonWarningSubject();
subject.register(new SmsObserver());
subject.register(new EmailObserver());
subject.notify("10级台风警报");
}
}
经典实现是一种同步阻塞式的实现方式,也就是要顺序的等所有观察者都处理完,被观察者才可以返回,进行下一个操作。实际工作中大部分时候,观察者的处理并没有先后关系,而且也不需要等所有观察者都处理结束了,被观察者才可以进行下一个操作。这个时候就可以使用异步非阻塞式的观察者模式。
异步非阻塞观察者模式
实现异步非阻塞有两种方式进程内多线程和进程间的消息队列中间件(如redis、ActiveMQ等)。进程间的消息队列本文不展开讲,本文重点讲一下进程内的异步非阻塞方式。
有两种写法可以实现异步非阻塞,一是观察者用线程处理消息通知,二是在被观察者中使用线程调用观察者。
//一是在观察者用线程处理消息通知
public class SmsObserver implements Observer {
public void handle(String massage) {
new Thread(new Runnable() {
@Override
public void run() {
//发布短信通知
}
}).start();
}
}
public class EmailObserver implements Observer {
public void handle(String massage) {
new Thread(new Runnable() {
@Override
public void run() {
//发布邮件通知
}
}).start();
}
}
//二是在被观察者中使用线程调用观察者
class TyphoonWarningSubject implements Subject {
List<Observer> observers = new ArrayList<>();
private Executor executor = Executors.newFixedThreadPool(3);
public void register(Observer observer) {
this.observers.add(observer);
}
public void remove(Observer observer) {
this.observers.remove(observer);
}
public void notify(String message) {
for (Observer observer : observers) {
executor.execute(new Runnable() {
@Override
public void run() {
observer.handle(message);
}
});
}
}
}
方法一不控制线程的数量,大并发时可能会有性能问题;方法二通过线程池控制线程数量,增加了原本不属于被观察者的工作,增加维护复杂度,违反单一职责原则。
事件总线(EventBus)调用观察者
一个系统中可能会有很多的不同需要观察的对象。这样就要有很多观察者与被观察者类。比如:
interface Observer {
public void handle(String massage);
}
interface Subject {
public void register(Observer observer);
public void remove(Observer observer);
public void notify(String message);
}
interface Observer2 {
public void send(String massage);
}
interface Subject2 {
public void register(Observer2 observer);
public void remove(Observer2 observer);
public void notify(String message);
}
interface Observer3 {
public void send(String massage);
}
interface Subject3 {
public void register(Observer3 observer);
public void remove(Observer3 observer);
public void notify(String message);
}
不同的观察与被观察者,其实动作其实都是一样的。被观察者注册、删除、发布通知,观察者处理通知,这么相似的情况完全可以通过 个框架统一处理,事件总线就是这样一个框架。Google Guava EventBus 是一个比较著名的 EventBus 框架,EventBus 框架的使用非常简单,只要类方法注释@Subscribe,这个类就能成为观察者。我们通过EventBus改写上面的例子。
class SmsObserver {
@Subscribe
public void handle(String massage) {
//发布短信通知
}
}
class EmailObserver {
@Subscribe
public void handle(String massage) {
//发布邮件通知
}
}
class TyphoonWarning {
List<Observer> observers = new ArrayList<>();
EventBus eventBus = new EventBus(); //同步阻塞总线
//EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(3)); //异步非阻塞总线
public void register(Object observer) {
this.eventBus.register(observer);
}
public void notify(String message) {
this.eventBus.post(message);
}
}
public class Demo {
public static void main(String[] args) {
Subject subject = new TyphoonWarningSubject();
subject.register(new SmsObserver());
subject.register(new EmailObserver());
subject.notify("10级台风警报");
}
}
可以看到用EventBus 框架,观察者模式实现简单很多,再也不需要观察者接口和被观察者接口,而且从同步阻塞改为异常阻塞改为创建AsyncEventBus类就可以。