本文来说下观察者模式
定义
定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用也非常广泛,比如:微信朋友圈动态通知、GPser生态圈消息通知、邮件通知、广播通知、桌面程序的事件响应等(如下图)。
介绍
- 观察者属于行为型模式。
- 观察者模式又被称作发布/订阅模式。
- 观察者模式主要用来解耦,将被观察者和观察者解耦,让他们之间没有没有依赖或者依赖关系很小。
UML类图
角色说明:
- Subject(抽象主题):又叫抽象被观察者,把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject(具体主题):又叫具体被观察者,将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
- Observer (抽象观察者) :为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
- ConcrereObserver(具体观察者):实现抽象观察者定义的更新接口,当得到主题更改通知时更新自身的状态。
代码实现
以送快递为例,快递员有时只是把快递拉到楼下,然后就通知收件人下楼去取快递。
创建抽象观察者
定义一个接到通知的更新方法,即收件人收到通知后的反应:
package cn.wideth.observer;
//抽象观察者
public interface Observer {
//更新方法
void update(String message);
}
创建具体观察者
实现抽象观察者中的方法,这里创建两个类,一个男孩类和一个女孩类,定义他们收到通知后的反应:
package cn.wideth.observer;
public class Boy implements Observer{
private String name;//名字
public Boy(String name) {
this.name = name;
}
@Override
public void update(String message) {//男孩的具体反应
System.out.println(name + ",收到了信息:" + message+"屁颠颠的去取快递.");
}
}
package cn.wideth.observer;
public class Girl implements Observer{
private String name;//名字
public Girl(String name) {
this.name = name;
}
@Override
public void update(String message) {//女孩的具体反应
System.out.println(name + ",收到了信息:" + message+"让男朋友去取快递~");
}
}
创建抽象主题
即抽象被观察者,定义添加,删除,通知等方法:
package cn.wideth.observer;
//抽象被观察者
public interface Observable {
//添加观察者
void add(Observer observer);
//删除观察者
void remove(Observer observer);
//通知观察者
void notify(String message);
}
创建具体主题
即具体被观察者,也就是快递员,派送快递时根据快递信息来通知收件人让其来取件:
package cn.wideth.observer;
import java.util.ArrayList;
import java.util.List;
//快递员
public class Postman implements Observable{
//保存收件人(观察者)的信息
private List<Observer> personList = new ArrayList<>();
@Override
public void add(Observer observer) {//添加收件人
personList.add(observer);
}
@Override
public void remove(Observer observer) {//移除收件人
personList.remove(observer);
}
@Override
public void notify(String message) {//逐一通知收件人(观察者)
for (Observer observer : personList) {
observer.update(message);
}
}
}
客户端测试
package cn.wideth.observer;
public class Main {
public static void main(String[] args) {
Observable postman = new Postman();
Observer boy1 = new Boy("李天宇");
Observer boy2 = new Boy("洪海洋");
Observer girl1 = new Girl("唐小美");
Observer girl2 = new Girl("张小小");
postman.add(boy1);
postman.add(boy2);
postman.add(girl1);
postman.add(girl2);
postman.notify("快递到了,请下楼领取,");
}
}
程序测试结果
JDK内部使用
实际上,JDK内部也内置了Observable(抽象被观察者),Observer(抽象观察者)这两个类,我们也可以直接拿来用,其代码如下:
public interface Observer {//(抽象观察者
//只定义了一个update方法
void update(Observable o, Object arg);
}
public class Observable {//抽象被观察者
private boolean changed = false;//定义改变状态,默认为false
private final ArrayList<Observer> observers;//定义一个观察者list
public Observable() {//构造函数,初始化一个观察者list来保存观察者
observers = new ArrayList<>();
}
//添加观察者,带同步字段的,所以是线程安全的
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!observers.contains(o)) {
observers.add(o);
}
}
//删除观察者
public synchronized void deleteObserver(Observer o) {
observers.remove(o);
}
//通知所以观察者,无参数
public void notifyObservers() {
notifyObservers(null);
}
//通知所有观察者,带参数
public void notifyObservers(Object arg) {
Observer[] arrLocal;
//加synchronized字段,保证多线程下操作没有问题
synchronized (this) {
if (!hasChanged())//这里做了是否发生改变的判断,是为了防止出现无意义的更新
return;
//ArrayList转换成一个临时的数组,这样就防止了通知,添加,移除同时发生可能导致的异常
arrLocal = observers.toArray(new Observer[observers.size()]);
clearChanged();///清除改变状态,设置为false
}
//遍历逐一通知
for (int i = arrLocal.length-1; i>=0; i--)
arrLocal[i].update(this, arg);
}
//清楚所有观察者
public synchronized void deleteObservers() {
observers.clear();
}
//设置被观察者为改变状态,设置为true
protected synchronized void setChanged() {
changed = true;
}
//清除改变状态,设置为false
protected synchronized void clearChanged() {
changed = false;
}
//返回当前的改变状态
public synchronized boolean hasChanged() {
return changed;
}
//观察者数量
public synchronized int countObservers() {
return observers.size();
}
}
应用场景
- 当一个对象的改变需要通知其它对象改变时,而且它不知道具体有多少个对象有待改变时。
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
- 解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
- 易于扩展,对同一主题新增观察者时无需修改原有代码。
缺点
- 依赖关系并未完全解除,抽象主题仍然依赖抽象观察者。
- 使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
- 可能会引起多余的数据通知。
基于Guava API轻松落地观察者模式
在这里,我还推荐给大家一个实现观察者模式非常好用的框架。API使用也非常简单,举个例子,先引入maven依赖包:
maven导入
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
创建侦听事件GuavaEvent:
package cn.wideth.guava;
import com.google.common.eventbus.Subscribe;
public class GuavaEvent {
@Subscribe
public void subscribe(String str){
System.out.println("执行subscribe方法,传入的参数是:" + str);
}
}
客户端测试代码:
package cn.wideth.guava;
import com.google.common.eventbus.EventBus;
public class GuavaEventTest {
public static void main(String[] args) {
//消息总线
EventBus eventBus = new EventBus();
GuavaEvent guavaEvent = new GuavaEvent();
eventBus.register(guavaEvent);
eventBus.post("Tom");
//从Struts到SpringMVC的升级
//因为Struts面向的类,而SpringMVC面向的是方法
//前面两者面向的是类,Guava面向是方法
//能够轻松落地观察者模式的一种解决方案
//MQ
}
}
测试程序结果
本文小结
本文详细介绍了观察者模式的信息,观察者模式是一种应用非常普遍的设计模式,通常被用在程序来对程序进行解耦。在开源的框架或者中间件中也有比较多的应用,比如在spring框架和zookeeper框架中都起着重要的作用。