<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">1.概念</span>
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式;
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
2.模式中的角色
抽象主题角色:Subject(被观察者角色)
把所有对观察者对象的引用保存在一个聚集里(ArrayList),每个主题都可以有任何数量的观察者;提供了增加和删除观察者对象接口及通知方法(只给所有已注册的观察者通知)。
具体主题角色:ConcreteSubject(具体被观察者角色)
将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有登记过的观察者发出通知。
观察者角色接口:Observer(抽象观察者)
为所有具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
具体观察者角色:ConcreteObserver(具体观察者角色)
存储与主题状态相同的状态,同时实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态保持协调;如果需要,还可以保持一个指向具体主题对象的引用。
3.举例(以报刊发布-订阅为例)
概念太抽象,精简下:一个被关注对象的状态在改变的同时,引起关注该对象的观察者状态同时改变,并要求它们之间的耦合度不能太高(这是关键)。
被关注对象:报社
观察者:小报刊亭(关注最新杂志)
状态:最新杂志
3.1简单说明
一个报社每次都会发布新消息(杂志),有3个小报刊亭,由于经费问题,他们未向报社订阅杂志,每次都是自己去报社取最新的杂志,由于取的时间有早有晚,导致它们的杂志有时不是最新的;由于杂志热销,现在3个小报刊亭要求每次都拿到最新杂志,他们一商量,决定向报社统一订阅,后续报社一发新杂志,就给他们送去,这样就能保证它们的杂志是最新的了(这就是观察者模式中push方式的一种,由报社主动推送指定状态,还有一种是pull方式,大家另外到网上搜搜)。
3.2场景描述
报社发布最新杂志为第十期杂志,由于关注方(观察者)报刊亭1,2未准时去取杂志,它们的最新杂志分别停留在第八期和第九期上(未使用观察者模式)。
------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------
被关注对象:杂志社[最新杂志=最新消息:第十期杂志]
关注方:报刊亭1, 最新杂志=最新消息:第八期杂志]
关注方:报刊亭2, 最新杂志=最新消息:第九期杂志]
关注方:报刊亭3, 最新杂志=最新消息:第十期杂志]
被关注对象:杂志社[最新杂志=最新消息:第十期杂志]
关注方:报刊亭1, 最新杂志=最新消息:第八期杂志]
关注方:报刊亭2, 最新杂志=最新消息:第九期杂志]
关注方:报刊亭3, 最新杂志=最新消息:第十期杂志]
报刊亭1,2,3向报社订阅了最新杂志,每次报社一发布最新杂志,就给它们送一份最新的杂志(使用观察者模式)。
------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----
被关注对象:杂志社[最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭1, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭2, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭3, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
被关注对象:杂志社[最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭1, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭2, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭3, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
3.3代码实现
实际只需要两个类就可以了,但为了尽量减少类之间的耦合度,进行了抽象类和接口的抽取,变成了四个类(接口和抽象类属于特殊的类)。
1.报社抽象类(被观察主题):内部维护所有订阅最新杂志的报刊亭(内部维护了一个已注册观察者集合)、报社添加报刊亭方法(注册观察者)、报社移除报刊亭方法(移除观察者)、通知所有报刊亭更新杂志方法(将新状态通知给所有已注册观察者);
2.实际报社类(实际被观察主题):内部维护了最新杂志(被关注对象),发布最新杂志方法(状态改变方法)-报社杂志改变及调用父类通知所有报刊亭杂志改变方法;
3.更新杂志接口(观察者的更新状态方法):内部声明一个更新杂志的方法,需要具体的子类实现(更灵活、同时降低耦合度,不同的子类可以有不同的实现方式);
4.具体报刊亭实现更新杂志接口:将旧杂志替换为新杂志(this.observerStatus = newStatus)。
1.报社抽象类(公共代码部分,不管是什么报社,都需要拥有这些代码,被观察主题Subject)
public abstract class Subject {
/**
* 已注册观察者集合(报刊亭集合)
*/
private List<Observer> observers = new ArrayList<Observer>();
/**
* 注册观察者(报社添加报刊亭方法)
* @param observer 待注册的观察者
*/
public void addObserver(Observer observer){
observers.add(observer);
}
/**
* 移除注册观察者(<span style="font-family: Arial, Helvetica, sans-serif;">报社移除报刊亭方法</span>)
* @param observer 待移除的注册观察者
*/
public void deleteObserver(Observer observer){
observers.remove(observer);
}
/**
* 将新状态通知给所有已注册观察者(关键点:给子类调用,报社发布新杂志,并给所有订阅的报刊亭一份最新杂志)
* @param newStatus
*/
public void notifyObservers(String newStatus){
for(Observer observer:observers){
observer.update(newStatus);
}
}
}
2.实际报社类(ConcreteSubject):继承抽象报社类,内部维护被关注对象(最新杂志,String status),状态改变方法(发布最新杂志)-本身杂志改变及调用父类通知所有报刊亭方法。
public class ConcreteSubject extends Subject {
private String status;//被关注状态:最新杂志
public ConcreteSubject(String status) {
this.status = status;
}
// 状态发生改变,自己改变同时通知各个观察者(报社发布最新杂志)
public void changeStatus(String newStatus) {
this.status = newStatus;
this.notifyObservers(newStatus);
}
@Override
public String toString() {
return "被关注对象:杂志社[最新杂志=" + status + "]";
}
}
3.更新杂志接口(观察者的更新状态方法):内部声明一个更新杂志的方法,需要具体的子类实现(更灵活、同时降低耦合度,不同的子类可以有不同的实现方式);
public interface Observer {
/**
* 观察者状态更新(最新杂志更新)
* @param status
*/
public void update(String status);
}
4.具体报刊亭实现更新杂志接口:将旧杂志替换为新杂志(this.observerStatus = newStatus)。
public class ConcreteObserver implements Observer{
private int id;
private String observerStatus;
public ConcreteObserver(int id, String observerStatus) {
this.id = id;
this.observerStatus = observerStatus;
}
@Override
public void update(String newStatus) {
this.observerStatus = newStatus;
}
@Override
public String toString() {
return "关注方:报刊亭" + id + ", 最新杂志="
+ observerStatus + "]";
}
}
<pre name="code" class="html">3.4测试
<span style="white-space:pre"> </span>public void testUpdate() throws Exception {
//创建4个对象:实际报社类(实际被观察主题)、三个报刊亭(观察者)及它们未关联前的状态
ConcreteSubject subject = new ConcreteSubject("最新消息:第十期杂志");
ConcreteObserver observer1 = new ConcreteObserver(1,"最新消息:第八期杂志");//由于某些原因,未获取到最新杂志消息(不正常)
ConcreteObserver observer2 = new ConcreteObserver(2,"最新消息:第九期杂志");//同上
ConcreteObserver observer3 = new ConcreteObserver(3,"最新消息:第十期杂志");//获取到最新消息,与主题保持同步(正常)
//打印报社和3个报刊亭的最新杂志
System.out.println("------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------");
System.out.println(subject);
System.out.println(observer1);
System.out.println(observer2);
System.out.println(observer3);
//为确保报刊亭杂志与报社保持一致,报刊亭订阅该杂志
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.addObserver(observer3);
//报社发布新杂志,查看报社及报刊亭的所有最新杂志是否一致
System.out.println("------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----");
subject.changeStatus("最新消息:第十一期杂志,SDR入篮了!");//重点:杂志发表新消息后,所有订阅方都必须获取到最新消息
System.out.println(subject);
System.out.println(observer1);
System.out.println(observer2);
System.out.println(observer3);
}
测试结果:
------报刊亭未订阅报社杂志前,最新杂志未全部保持一致------
被关注对象:杂志社[最新杂志=最新消息:第十期杂志]
关注方:报刊亭1, 最新杂志=最新消息:第八期杂志]
关注方:报刊亭2, 最新杂志=最新消息:第九期杂志]
关注方:报刊亭3, 最新杂志=最新消息:第十期杂志]
------报刊亭订阅报社杂志后,两者发布的最新杂志保持一致了-----
被关注对象:杂志社[最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭1, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭2, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]
关注方:报刊亭3, 最新杂志=最新消息:第十一期杂志,SDR入篮了!]