观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
观察者模式属于行为模式,其意图是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在制作系统的过程中,将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了他们的可重用性。这一个模式的关键对象是目标和观察者。一个目标可以有任意数目的依赖它的观察者,一旦目标的状态发生改变,所有的观察者都得到通知,作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。这种交互也称为发布-订阅模式,目标是通知的发布者。他发出通知时并不需要知道谁是他的观察者,可以有任意数据的观察者订阅并接收通知。
适用性:在以下的任一情况下可以使用观察者模式:
1 当一个抽象模型的两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对象中可以使他们各自独立的改变和复用。
2 当对一个对象的改变需要同时改变其它对象,而不知道具体由多少对象有待改变。
3 当一个对象必须通知其他对象,而它又不能假定其他对象是谁,换言之,你不希望这些对象是紧密耦合的。
观察者模式所涉及的角色有:
● 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
● 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
● 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
● 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
推模型和拉模型
在观察者模式中,又分为推模型和拉模型两种方式。
● 推模型
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
● 拉模型
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
根据上面的描述,发现前面的例子就是典型的推模型,下面给出一个拉模型的实例。
拉模型的抽象观察者类
拉模型通常都是把主题对象当做参数传递。
推模型例子:抽象主题角色类:
public abstract class Subject {
/**
* 用来保存注册的观察者对象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注册观察者对象
* @param observer 观察者对象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 删除观察者对象
* @param observer 观察者对象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}
具体主题角色类
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主题状态为:" + state);
//状态发生改变,通知各个观察者
this.nodifyObservers(state);
}
}
抽象观察者角色类
public interface Observer {
/**
* 更新接口
* @param state 更新的状态
*/
public void update(String state);
}
具体观察者角色类
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(String state) {
/**
* 更新观察者的状态,使其与目标的状态保持一致
*/
observerState = state;
System.out.println("状态为:"+observerState);
}
}
客户端类
public class Client {
public static void main(String[] args) {
//创建主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建观察者对象
Observer observer = new ConcreteObserver();
//将观察者对象登记到主题对象上
subject.attach(observer);
//改变主题对象的状态
subject.change("new state");
}
}
运行结果如下
在运行时,这个客户端首先创建了具体主题类的实例,以及一个观察者对象。然后,它调用主题对象的attach()方法,将这个观察者对象向主题对象登记,也就是将它加入到主题对象的聚集中去。
这时,客户端调用主题的change()方法,改变了主题对象的内部状态。主题对象在状态发生变化时,调用超类的notifyObservers()方法,通知所有登记过的观察者对象。
拉模型的抽象观察者类
拉模型通常都是把主题对象当做参数传递。
public interface Observer {
/**
* 更新接口
* @param subject 传入主题对象,方面获取相应的主题对象的状态
*/
public void update(Subject subject);
}
拉模型的具体观察者类
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(Subject subject) {
/**
* 更新观察者的状态,使其与目标的状态保持一致
*/
observerState = ((ConcreteSubject)subject).getState();
System.out.println("观察者状态为:"+observerState);
}
}
拉模型的抽象主题类
拉模型的抽象主题类主要的改变是nodifyObservers()方法。在循环通知观察者的时候,也就是循环调用观察者的update()方法的时候,传入的参数不同了。
public abstract class Subject {
/**
* 用来保存注册的观察者对象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注册观察者对象
* @param observer 观察者对象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 删除观察者对象
* @param observer 观察者对象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
public void nodifyObservers(){
for(Observer observer : list){
observer.update(this);
}
}
}
拉模型的具体主题类
跟推模型相比,有一点变化,就是调用通知观察者的方法的时候,不需要传入参数了。
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主题状态为:" + state);
//状态发生改变,通知各个观察者
this.nodifyObservers();
}
}
两种模式的比较
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
参考文献:http://blog.youkuaiyun.com/zhangming1013/article/details/25248269
http://tianli.blog.51cto.com/190322/40455