观察者模式
别名"发布-订阅模式", "源-监听器模式"
引入
需要满足这样一种需求: 一个对象(观察目标)状态发生改变, 需要通知另外一些对象(观察者), 这些对象将做出反应, 一个观察目标可以对应多个观察者, 且观察者间没有联系, 可以随时增删观察者.
定义
定义一种对象间的一对多关系, 使得每当一的一方对象状态放生变化, 通知多的一方
结构
代码分析
/**
* 抽象观察者
*
* @version 0.1
* @author xy
* @date 2018年1月25日 下午9:29:32
*/
public interface IObserver {
void update(Target t);
}
/**
* observe target
*
* @version 0.1
* @author xy
* @date 2018年1月25日 下午11:19:52
*/
public abstract class Target {
/**
* observer list
*/
private List<IObserver> observerList = new ArrayList<IObserver>();
/**
* add observer
* @param observer
*/
public void add(IObserver observer) {
observerList.add(observer);
}
/**
* remove observer
* @param observer
*/
public void remove(IObserver observer) {
observerList.remove(observer);
}
/**
* change status
*/
public void change() {
System.out.println("Target状态发生变化");
this.inform();
}
/**
* inform observers
*/
private void inform() {
for(IObserver observer: observerList) {
observer.update(this);
}
}
}
///////////////////// 如何使用? ////////////////////////////////////////////
// 先定义 observers, 多个, 实现自己的 update()
/**
* concrete observer1
*
* @version 0.1
* @author xy
* @date 2018年1月25日 下午11:42:45
*/
public class Observer1 implements IObserver {
@Override
public void update(Target t) {
System.out.println("Observer1观察到" + t.getClass().getSimpleName()+ "发生变化");
}
}
/**
* concrete observer2
*
* @version 0.1
* @author xy
* @date 2018年1月25日 下午11:45:16
*/
public class Observer2 implements IObserver {
@Override
public void update(Target t) {
System.out.println("Observer2观察到" + t.getClass().getSimpleName() + "发生变化.");
}
}
/**
*
*
* @version 0.1
* @author xy
* @date 2018年1月25日 下午11:53:28
*/
public class MyTarget extends Target {
// ...
// 测试
public static void main(String[] args) {
Target t = new MyTarget();
IObserver observer1 = new Observer1();
IObserver observer2= new Observer2();
t.add(observer1);
t.add(observer2);
t.change();
}
}
模式分析
- 观察目标和观察者解耦, 同步变化
- 一个观察目标有任意个观察者, 这些观察者派生自同一个接口
优点:
- 符合开闭原则
- 实现表示层and数据层分离, 且可以有各种不同的表示层作为观察者监控数据层的变化
缺点:
- 观察者很多时耗时严重
- 观察者和目标之间有循环依赖, 可能会触发循环调用导致系统崩溃
- 观察者模式无法让观察者知道目标是如何变化的, 仅仅只是知道目标发生了变化
适用场景
- 一个对象改变必须通知其他对象, 而不知道这些对象具体名称, 具体个数
- 拓展观察者模式, 创建一个触发链, A影响b, b影响c.....
应用实例
java.util.Observer java.util.EventListener javax.servlet.http.HttpSessionBindingListener RxJava
凡是设计到1:1 or 1:N 的对象交互场景均可使用观察者模式
jdk中的观察者模式api
jdk中的观察者模式api:
//观察者接口,每一个观察者都必须实现这个接口
public interface Observer {
//这个方法是观察者在观察对象产生变化时所做的响应动作,从中传入了观察的对象和一个预留参数
void update(Observable o, Object arg);
}
//被观察者类
public class Observable {
//这是一个改变标识,来标记该被观察者有没有改变
private boolean changed = false;
//持有一个观察者列表
private Vector obs;
public Observable() {
obs = new Vector();
}
//添加观察者,添加时会去重
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//删除观察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//notifyObservers(Object arg)的重载方法
public void notifyObservers() {
notifyObservers(null);
}
//通知所有观察者,被观察者改变了,你可以执行你的update方法了。
public void notifyObservers(Object arg) {
//一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。
Object[] arrLocal;
//注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的
//也就是说,在我获取到观察者列表之前,不允许其他线程改变观察者列表
synchronized (this) {
//如果没变化直接返回
if (!changed)
return;
//这里将当前的观察者列表放入临时数组
arrLocal = obs.toArray();
//将改变标识重新置回未改变
clearChanged();
}
//注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其他线程可以改变观察者列表
//但是这并不影响我们当前进行的操作,因为我们已经将观察者列表复制到临时数组
//在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
//删除所有观察者
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//标识被观察者被改变过了
protected synchronized void setChanged() {
changed = true;
}
//标识被观察者没改变
protected synchronized void clearChanged() {
changed = false;
}
//返回被观察者是否改变
public synchronized boolean hasChanged() {
return changed;
}
//返回观察者数量
public synchronized int countObservers() {
return obs.size();
}
}
尝试着用一下: 示例, 读者(观察者)&作者(被观察者),
类图是这样的:
//读者类,要实现观察者接口
public class Reader implements Observer{
private String name;
public Reader(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
//读者可以关注某一位作者,关注则代表把自己加到作者的观察者列表里
public void subscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).addObserver(this);
}
//读者可以取消关注某一位作者,取消关注则代表把自己从作者的观察者列表里删除
public void unsubscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
}
//当关注的作者发表新小说时,会通知读者去看
public void update(Observable o, Object obj) {
if (o instanceof Writer) {
Writer writer = (Writer) o;
System.out.println(name+"知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!");
}
}
}
//作者类,要继承自被观察者类
public class Writer extends Observable{
private String name;//作者的名称
private String lastNovel;//记录作者最新发布的小说
public Writer(String name) {
super();
this.name = name;
WriterManager.getInstance().add(this);
}
//作者发布新小说了,要通知所有关注自己的读者
public void addNovel(String novel) {
System.out.println(name + "发布了新书《" + novel + "》!");
lastNovel = novel;
setChanged();
notifyObservers();
}
public String getLastNovel() {
return lastNovel;
}
public String getName() {
return name;
}
}
//管理器,保持一份独有的作者列表
public class WriterManager{
private Map<String, Writer> writerMap = new HashMap<String, Writer>();
//添加作者
public void add(Writer writer){
writerMap.put(writer.getName(), writer);
}
//根据作者姓名获取作者
public Writer getWriter(String name){
return writerMap.get(name);
}
//单例
private WriterManager(){}
public static WriterManager getInstance(){
return WriterManagerInstance.instance;
}
private static class WriterManagerInstance{
private static WriterManager instance = new WriterManager();
}
}
//客户端调用
public class Client {
public static void main(String[] args) {
//假设四个读者,两个作者
Reader r1 = new Reader("谢广坤");
Reader r2 = new Reader("赵四");
Reader r3 = new Reader("七哥");
Reader r4 = new Reader("刘能");
Writer w1 = new Writer("谢大脚");
Writer w2 = new Writer("王小蒙");
//四人关注了谢大脚
r1.subscribe("谢大脚");
r2.subscribe("谢大脚");
r3.subscribe("谢大脚");
r4.subscribe("谢大脚");
//七哥和刘能还关注了王小蒙
r3.subscribe("王小蒙");
r4.subscribe("王小蒙");
//作者发布新书就会通知关注的读者
//谢大脚写了设计模式
w1.addNovel("设计模式");
//王小蒙写了JAVA编程思想
w2.addNovel("JAVA编程思想");
//谢广坤取消关注谢大脚
r1.unsubscribe("谢大脚");
//谢大脚再写书将不会通知谢广坤
w1.addNovel("观察者模式");
}
}
观察者模式的另一种形态: 事件驱动模型
eg:例如Tomcat中的监听器listener
两种形态区别:
观察者模式中观察者的响应理论上讲针对特定的被观察者是唯一的(说理论上唯一的原因是,如果你愿意,你完全可以在update方法里添加一系列的elseif去产生不同的响应,但LZ早就说过,你应该忘掉elseif),而事件驱动则不是,因为我们可以定义自己感兴趣的事情,比如刚才,我们可以监听作者发布新书,我们还可以在监听器接口中定义其它的行为。再比如tomcat中,我们可以监听servletcontext的init动作,也可以监听它的destroy动作。
虽然事件驱动模型更加灵活,但也是付出了系统的复杂性作为代价的,因为我们要为每一个事件源定制一个监听器以及事件,这会增加系统的负担,各位看看tomcat中有多少个监听器和事件类就知道了。
另外观察者模式要求被观察者继承Observable类,这就意味着如果被观察者原来有父类的话,就需要自己实现被观察者的功能,当然,这一尴尬事情,我们可以使用适配器模式弥补,但也不可避免的造成了观察者模式的局限性。事件驱动中事件源则不需要,因为事件源所维护的监听器列表是给自己定制的,所以无法去制作一个通用的父类去完成这个工作。
被观察者传送给观察者的信息是模糊的,比如update中第二个参数,类型是Object,这需要观察者和被观察者之间有约定才可以使用这个参数。而在事件驱动模型中,这些信息是被封装在Event当中的,可以更清楚的告诉监听器,每个信息都是代表的什么。
针对 读者&作者的例子, 对应的时间驱动模型修改如下:
此时, 作者就是事件源,而读者就是监听器
//定义作者事件, 这代表了一个作者事件: 发布新书
// 这个事件当中一般就是包含一个事件源,在这里就是作者, 通过 constructor 传入
//
public class WriterEvent extends EventObject{
private static final long serialVersionUID = 8546459078247503692L;
public WriterEvent(Writer writer) {
super(writer);
}
public Writer getWriter(){
return (Writer) super.getSource();
}
}
//对应的 需要有一个监听器监听这个事件, 对"指定事件" 做出反应
public interface WriterListener extends EventListener{
// 参数: 被监听的事件
void addNovel(WriterEvent writerEvent);
}
//作者类
// 可以看到,作者类的主要变化是添加了一个自己的监听器列表,我们使用set是为了它的天然去重效果,并且提供给外部注册和注销的方法,与观察者模式相比,jdk中的观察者api对于这个功能本身是由基类Observable 默认提供的,不过观察者模式中有统一的观察者Observer接口,但是监听器没有,虽说有EventListener这个超级接口,但它毕竟没有任何行为。所以我们一般需要维持一个自己特有的监听器列表。
public class Writer{
private String name;//作者的名称
private String lastNovel;//记录作者最新发布的小说
private Set<WriterListener> writerListenerList = new HashSet<WriterListener>();//作者类要包含一个自己监听器的列表
public Writer(String name) {
super();
this.name = name;
WriterManager.getInstance().add(this);
}
//作者发布新小说了,要通知所有关注自己的读者
public void addNovel(String novel) {
System.out.println(name + "发布了新书《" + novel + "》!");
lastNovel = novel;
fireEvent();
}
//触发发布新书的事件,通知所有监听这件事的监听器
private void fireEvent(){
WriterEvent writerEvent = new WriterEvent(this);
for (WriterListener writerListener : writerListenerList) {
writerListener.addNovel(writerEvent);
}
}
//提供给外部注册成为自己的监听器的方法
public void registerListener(WriterListener writerListener){
writerListenerList.add(writerListener);
}
//提供给外部注销的方法
public void unregisterListener(WriterListener writerListener){
writerListenerList.remove(writerListener);
}
public String getLastNovel() {
return lastNovel;
}
public String getName() {
return name;
}
}
// 读者类的变化,首先本来是实现Observer接口,现在要实现WriterListener接口,响应的update方法就改为我们定义的addNovel方法,当中的响应基本没变。另外就是关注和取消关注的方法中,原来是给作者类添加观察者和删除观察者,现在是注册监听器和注销监听器,几乎是没什么变化的。
public class Reader implements WriterListener{
private String name;
public Reader(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
//读者可以关注某一位作者,关注则代表把自己加到作者的监听器列表里
public void subscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).registerListener(this);
}
//读者可以取消关注某一位作者,取消关注则代表把自己从作者的监听器列表里注销
public void unsubscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).unregisterListener(this);
}
public void addNovel(WriterEvent writerEvent) {
Writer writer = writerEvent.getWriter();
System.out.println(name+"知道" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》,非要去看!");
}
}