1.观察者模式概要
观察者模式又称为发布/订阅(Publish/Subscribe)模式,因此我们可以用报纸期刊的订阅来形象的说明:
报社方负责出版报纸.
你订阅了该报社的报纸,那么只要报社发布了新报纸,就会通知你,或发到你手上.
如果你不想再读报纸,可以取消订阅,这样,报社发布了新报纸就不会再通知你.
理解其实以上的概念,就可以理解观察者模式,观察者模式中有主题(Subject)和观察者(Observer),分别对应报社和订阅用户(你).观察者模式定义了对象之间的一对多的依赖关系,这样,当"一"的一方状态发生变化时,它所依赖的"多"的一方都会收到通知并且自动更新.如图:
简单来说既当主题更新时,所有观察者都会收到通知
2、观察者模式案例代码
主题类(Subject)
//主题接口
interface Subject {
//添加观察者
void registerObserver(Observer obj);
//移除观察者
void deleteObserver(Observer obj);
//当主题方法改变时,这个方法被调用,通知所有的观察者
void notifyObserver();
}
观察者类(Oserver)
interface Observer {
//当主题状态改变时,会将一个String类型字符传入该方法的参数,每个观察者都需要实现该方法
public void update(String info);
}
3、案例代码
假设我们是一个报社,我们需要给订阅我们的所有用户发送报纸,用户可以随时订阅和取消订阅
①编写Obverse接口
public interface Observer {
void updata(String message);
}
②编写Subject接口
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObserver();
}
③编写News报社主题类
public class News implements Subject {
private ArrayList observeList;
private String date;
public News() {
this.observeList = new ArrayList();
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
@Override
public void registerObserver(Observer o) {
observeList.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observeList.indexOf(o);
if (i > 0){
observeList.remove(i);
}
}
@Override
public void notifyObserver() {
for (int i = 0; i < observeList.size(); i++) {
Observer o = (Observer) observeList.get(i);
o.updata("得到"+ date +"报纸");
}
}
}
④编写用户类
public class User1 implements Observer{
private String name;
public User1(String name, Subject news) {
this.name = name;
news.registerObserver(this);
}
@Override
public void updata(String message) {
System.out.println(name + message);
}
}
⑤测试代码
public static void main(String[] args) {
//初始化声明
News news = new News();
User1 zhao = new User1("赵先生",news);
User1 qian = new User1("钱先生",news);
User1 sun = new User1("孙先生",news);
User1 li = new User1("李先生",news);
//修改日期
news.setDate("3月17日");
news.notifyObserver();
System.out.println("-------分割线------");
news.setDate("3月18日");
news.notifyObserver();
}
运行结果为
这样,我就完成了观察者模式中一人更新全员都能收到的情况了
4、问题来了?如果我不想全部接受怎么办
假如我是一个报纸订阅者,我工作繁忙只有周末才有空看报纸,而且我周一至周五不想接受报社推送的报纸
有没有办法能让我自己主动去拿报纸,而不是被动的接受报纸呢?
简单来说,推只会推送报社想要推送的数据
拉可以获取用户自己想要的数据
Java内置的API帮我们解决了这一点
在java.util包下面存在这两个类,Observable类和Observer接口
前者为主题类,后者为观察者接口
我们先看下观察者接口
public interface Observer {
void update(Observable o, Object arg);
}
观察者接口和我们上面设计的大体相同,但是不同的是,这里多了一个参数Observable
这个参数能够让我们知道是那个主题发来的信息,毕竟用户定报纸又不是只定一家!
我们再来看一下主题类(Observable)
public class Observable {
private boolean changed = false;
private Vector<Observer> 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);
}
e java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers() {
notifyObservers(null);
}
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 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();
}
}
上面的代码很容易理解,通过函数名字就可以完全知道这个函数是干嘛的
我们重点介绍一下其中的**setChanged()**这个方法
调用setCahnged方法后,会把changed这个属性变为true
这个changed又会在那被调用呢
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);
}
在通过更新所有观察者的更新中,这个changed被调用了
如果为false则不通知,只有为true才通知
为什么要这样做,这样有必要吗?
其实是有必要的,我们可以给通知划分一个条件,比如报纸凌晨三点印刷完毕,我们不可能离开通知所有定报纸的用户来领,毕竟他们现在都还在睡觉呢,我们可以通过这个changed来限定一个时间,比如说到了7点中才告诉用户,今天的报纸好了
好了,介绍了这么多,我们通过使用Java内置的API看看怎么实现让用户主动拉取消息的功能
首先设计Subject类
public class News extends Observable {
private ArrayList observeList;
private String date;
private String ad;
public News() {
this.observeList = new ArrayList();
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = "得到日期为" + date + "的报纸";
setChanged();
notifyObservers(this.date);
}
public String getAd() {
return ad;
}
public void setAd(String ad) {
this.ad = "通过报纸获得了" + ad + "的消息";
}
}
在主题类里面,我们设置了两个属性,一个是广告一个是今日的报纸,只要日期改变,立马告知所有订阅者,今日的报纸出来了。但是广告改变并不会通知所有人,广告只提供给需要的人群,需要他们自己去获取
接下来,我们设计两个用户类,1是接受全部报纸推送的用户User1,2是只想看报纸里面广告的User2(因为每天报纸里面的广告也会更新)
首先我们看User1
public class User1 implements Observer {
private String name;
public User1(String name, Observable o) {
this.name = name;
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + arg);
}
}
updata里面的arg参数,就是Subject类想主动推送给我们的东西,User1不管报社会推送什么,都全盘接受
接下来是User2类
public class User2 implements Observer {
private String name;
public User2(String name, Observable o) {
this.name = name;
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + ((News) o).getAd());
}
}
在User2类中,User2当发现推送的时候,选择不接受arg参数,选择自己变成Subject类,自己去取Subject类里面的东西
在代码中,自己去获取了广告的信息
主函数代码
public static void main(String[] args) {
//初始化声明
News news = new News();
User1 zhao = new User1("赵先生",news);
User1 qian = new User1("钱先生",news);
User2 sun = new User2("孙先生",news);
User2 li = new User2("李先生",news);
//修改日期
news.setAd("今日不打折");
news.setDate("3月17日");
System.out.println("-------分割线------");
news.setAd("今天打折");
news.setDate("3月18日");
}
运行代码
当我们改变情况时再看看
我们只修改广告
我们发现什么都没有输出,这就表明Subject类并没有推送数据
我们也无法主动获取Subject类里面的东西
5、推和拉的好处
“推”的方式是指,Subject维护一份观察者的列表,每当有更新发生,Subject会把更新消息主动推送到各个Observer去。
“拉”的方式是指,各个Observer维护各自所关心的Subject列表,自行决定在合适的时间去Subject获取相应的更新数据。
“推”的好处包括:
1、高效。如果没有更新发生,不会有任何更新消息推送的动作,即每次消息推送都发生在确确实实的更新事件之后,都是有意义的。
2、实时。事件发生后的第一时间即可触发通知操作。
3、可以由Subject确立通知的时间,可以避开一些繁忙时间。
4、可以表达出不同事件发生的先后顺序。
“拉”的好处包括:
1、如果观察者众多,Subject来维护订阅者的列表,可能困难,或者臃肿,把订阅关系解脱到Observer去完成。
2、Observer可以不理会它不关心的变更事件,只需要去获取自己感兴趣的事件即可。
3、Observer可以自行决定获取更新事件的时间。
4、拉的形式可以让Subject更好地控制各个Observer每次查询更新的访问权限。