概念
定义:
观察者模式定义了对象间的一种一对多的依赖关系。当“一”的对象状态发生变化时,有依赖于它的所有对象都得到通知并且自动更新,其本质是触发联动
结构:
观察者模式主要有两个角色,”主题”(Subject)和”观察者”()。如果把这种一对多的关系比作报社和订报者,那么“报社”就是主题,订报者就是”观察者”,所以观察者模式也叫“发布”-“订阅”模式
关键说明:
1.事实上,因为一对多,所以一的这一方,即主题必定有存储这个关系映射的,所以主题会有一个List用于存储观察者。
2.既然是List,既然是报社,那么必然存在“订阅”与”取消订阅”,“添加”与”取消”,所以一个主题类也必然需要这两个方法。
3.观察者模式是如何做到自身变化一改变,所有依赖于它的对象都得到通知?事实上,做到以下几点就行了。
- 在观察者内部,必须订阅了主题
- 主题方,在自身状态发动变化的时候,遍历主题列表下的所有观察者,调用他们的更新方法,将数据发送过去即可。
- 这样就实现了主题发生变化,观察者都知道的一对多关系了
小实例
这是一个报社和订报者的实例。报社一旦出新报纸,所有的订阅者都能得到一份新报纸
接口
/**
* 目标对象,作为被观察者
*/
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
/**
* 观察者,比如报纸的读者
*/
public interface Observer {
/**
* 被通知的方法
* @param subject 具体的目标对象,可以获取报纸的内容
*/
public void update(Subject subject);
}
实例
public class NewsPaper implements Subject{
/**
* 用来保存注册的观察者对象,也就是报纸的订阅者
*/
private List<Observer> observers;
private String content;
/**
* 报纸的读者需要先向报社订阅,先要注册
* @param reader 报纸的读者
* @return 是否注册成功
*/
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
/**
* 报纸的读者可以取消订阅
* @param reader 报纸的读者
* @return 是否取消成功
*/
@Override
public void removeObserver(Observer o) {
int index = observers.indexOf(o);
if(index>=0){
observers.remove(index);
}
}
/**
* 当每期报纸印刷出来后,就要迅速的主动的被送到读者的手中,
* 相当于通知读者,让他们知道
*/
@Override
public void notifyObservers() {
for(Observer o :observers){
o.update(this);
}
}
public NewsPaper() {
observers = new ArrayList<Observer>();
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
notifyObservers();
}
}
public class Reader implements Observer{
private Subject subject;
public Reader(Subject subject) {
this.subject = subject;
//注册阅读者
subject.registerObserver(this);
}
@Override
public void update(Subject subject) {
if(subject instanceof NewsPaper){
//这是采用拉的方式
String content = ((NewsPaper)subject).getContent();
display(content);
}
}
public void display(String content){
System.out.println("content = " + content);
}
}
测试
public class Client {
public static void main(String[] args) {
//创建一个报纸,作为被观察者
NewsPaper subject = new NewsPaper();
//创建阅读者,也就是观察者
Reader reader = new Reader(subject);
Reader reader2 = new Reader(subject);
Reader reader3 = new Reader(subject);
//要出报纸啦
subject.setContent("新的新闻出来啦");
}
}
观察者模式”推”和”拉”
观察者模式有推拉两种形式,上面展示的是拉的方式,什么叫拉,即订阅者主动向主题去拿取数据。核心代码如下:
o.update(this);
这是在subject类内的notifyObservers方法,它采取什么样的形式去通知呢?它把自己当做参数传送过去了。
String content = ((NewsPaper)subject).getContent();
这是Observer类内的update方法,它怎么得到通知的呢?它主动从参数中拉取它需要的信息。
综上所述,这是观察者拉的模式。
那么观察者推的模式是怎样的?
事实上,只是参数的问题。拉的方式,主题将自身传过去,让观察者自己从这个对象中拉取。那么推的方式,就是将具体的通知推过去,观察者就能直接使用这个通知。核心代码如下:
o.update(content);
String content = content;
当然,你需要把观察者的update方法的形参定义为String类型,不然是不可能通过的。
java中内置的观察者模式
我们看起来会发现,其实Subject接口和Observer接口事实上很简单,难道我们以后使用观察者模式都需要再做这么两个接口吗?总是这样重复造轮子?
事实上不用的,java已经实现了观察者模式的相关接口,这两个重要的接口 (java.util.Observable)和(java.util.Observer)
是的,观察者还是叫观察者,但是主题变成了可观察者了,但是大概意思还是一致的。
那么,我们如何把一个对象变成观察者呢?三个步骤
1.对象实现观察者接口 (java.util.Observer)
2.调用实现Observable接口对象的addObserver方法
3.如果不想当该Observable接口的观察这,调用deleteObserver()方法
那么如何把一个对象变成可观察者(主题)呢?
1.扩展java.util.Observable,注意,是扩展,所以是继承
2.先调用setChanged()方法,标记状态为已改变
3.调用notifyObservers()或notifyObservers(Object arg)方法中的一个
实例
/**
* 真正的读者,为了简单就描述一下姓名
*/
public class Reader implements java.util.Observer{
/**
* 读者的姓名
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void update(Observable o, Object obj) {
//同时支持 推模型 和 拉模型
//这是采用推的方式
System.out.println(name+"收到报纸了,阅读先。目标推过来的内容是==="+obj);
//这是获取拉的数据
System.out.println(name+"收到报纸了,阅读先。主动到目标对象去拉的内容是==="
+((NewsPaper)o).getContent());
}
}
/**
* 报纸对象,具体的目标实现
*/
public class NewsPaper extends java.util.Observable{
/**
* 报纸的具体内容
*/
private String content;
/**
* 获取报纸的具体内容
* @return 报纸的具体内容
*/
public String getContent() {
return content;
}
/**
* 示意,设置报纸的具体内容,相当于要出版报纸了
* @param content 报纸的具体内容
*/
public void setContent(String content) {
this.content = content;
//内容有了,说明又出报纸了,那就通知所有的读者
//注意在用Java中的Observer模式的时候,这句话不可少
this.setChanged();
//然后主动通知,这里用的是推的方式
// this.notifyObservers(this.content);
//如果用拉的方式,这么调用
this.notifyObservers();
}
}
public class Client {
public static void main(String[] args) {
//创建一个报纸,作为被观察者
NewsPaper subject = new NewsPaper();
//创建阅读者,也就是观察者
Reader reader1 = new Reader();
reader1.setName("张三");
Reader reader2 = new Reader();
reader2.setName("李四");
Reader reader3 = new Reader();
reader3.setName("王五");
//注册阅读者
subject.addObserver(reader1);
subject.addObserver(reader2);
subject.addObserver(reader3);
//要出报纸啦
subject.setContent("本期内容是观察者模式");
System.out.println("----------->");
}
}
这个我觉得重点在于推拉两种模式,即notifyObservers()和notifyObservers(Object obj)两个方法,第一个方法表示采用拉的方式,第二个方法表示采用推的方式。具体我们可以看下源码
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public void notifyObservers() {
notifyObservers(null);
}
notifyObservers()方法,采用拉的方式,它会将null传过去。那么在观察者出的update获取到的参数就必定为null了,但是观察者仍然可以用拉取的方式拿到数据
notifyObservers(Object arg)方法,采用推的方式,将参数送给观察者,观察者可以直接拿到数据。
关于可观察者为什么必须调用.setChanged()方法,这里也有给一个说明,因为changed必须为true,才能执行消息通知的功能。
关于通知的顺序
对比我们自己造轮子和使用java内置的轮子,你会发现,通知的顺序不一样。本来是ABC顺序,但是java内置可能不是按这样的顺序。
但是我们需要知道,我们的重点是主题发生变化时,观察者得到通知,其先后顺序不应该得到具体确定,这也不是我们业务必须要的功能,多个观察者之间的功能也应该是平行的,相互之间也不应该有类似链条一样的先后依赖关系
现实中JDK的观察者模式
现实中有一个非常著名的观察者模式,就是Swing下的监听器。我们都知道,swing可以在一个组件上加上很多监听器addXXXListener。比如,一个button按钮,我们可以给他加上左键点击,右键点击等等触发事件。
这里主题就是组件,监听器就是观察者!这就是观察者模式了,不过这是变异了的观察者模式,它需要根据主题的不同状态变化来产生不一样的通知,去通知监听器说该组件已经被触发了。
所以Swing下的观察者模式是需要根据主题的不同变化区别通知不同的观察者,那么这是如何做到的呢?这就是观察者模式的简单变形–区别对待观察者
区别对待观察者
举个例子:微信的朋友圈,有多种可见,给某人看,不给某人看,所有人都可以看。
我们就能区别三种观察者了。
那么,我们可以有两种做法:
1.目标可以通知所有人,即三种观察者我们都通知到,但是对于非这类观察者,我们不做任何操作。
比如:我们设为给某人看,但是微信直接把信息通知给了所有人,但是除了某人,其他人不做任何操作,那么其他人也不会看到这条朋友圈了
2.在目标内部进行判断,如果非该类观察者,那就不通知了,只通知这类观察者。
比如:我们设为给某人看,微信内部就判断,其他人不是某人,那么其他人不收到通知。而某人刚好就是这类观察者,那么某人就会得到通知,且做出操作
一个观察者观察多个目标的问题
事实上我们从现实的swing还可以学到一招,那就是:
如果是一个观察者观察多个目标,那该怎么办?
学习的时候,视频描述了两种,但我发现还可以有第三种,事实上因人而异,可能还会有第四种,第五种等等。
第一种:
观察者的更新方法多一个形参,不同的目标传递不一样的实参,更新方法内根据参数判断是哪个目标。
第二种:
定义不一样的回调方法,这个回调方法指的就是更新方法。比如观察者观察了A主题,B主题,C主题。那么就定义一个A主题的更新方法,B主题的更新方法,C主题的更新方法
第三种:
就是我上面用到的使用的,但是需要是拉取的方式,以超类作为参数,面向接口编程,使用instanceof进行判断是哪个目标。