一、定义
观察者模式在GOF上是这么定义的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种对象间的交互也叫发布-订阅。
二、场景设计
考虑这样一种应用场景:一组数据实时动态变化,现在要对这组数据进行可视化表示。表示形式可能又很多:柱状图,表格,饼状图等。数据的变化要及时反映在可视化的表示中。这就是典型的发布-订阅场景,各种可视化形式要观察着数据的状态。一旦数据发生变化,变化要传递给各个正在观察的可视化表示形式。
同时,要注意到可视化表示形式可能有多个,数据不应该对哪些观察者正在观察做出任何的假设。这里将维护数据的类称为ConcreteSubject,将各个作为观察者的可视化形式的类称为ConcreteObserver,ConcreteObserver可能有多种类型。基于面向对象的设计原则来考虑:将变化的和不变的分离出来。可以抽象出一个Observer类,该类中维护Update()虚方法,在这个例子中,该方法代表各个可视化形式的更新方式。通过这个抽象,可以向被观察者(数据)屏蔽观察者的具体信息。
这样抽象之后,可以在ConcreteSubject类中维护一个Observer类的链表,当有一个ConcreteObserver开始观察数据时,首先要注册到这个链表中。这样当ConcreteSubject状态发生改变后,可以通过遍历这个链表来向所有注册的观察者发布相应变化。
至此,发布-订阅功能已经能实现了。但是,若是被观察者有多个,即ConcreteSubject有多种类型,那这套设计就显得不灵活了。这里可以对ConcreteSubject进行一层抽象。抽象出Subject类,这个类维护Observer类链表中元素的增加,删除以及状态变化后的遍历广播。而被观察者的状态改变方法Set()和状态获取方法Get(),自然由具体类ConcreteSubject各自来维护。在这种情况下,要扩展Observer类中的Update接口,以使得观察者知道通知是由哪个被观察者送来。被观察者可以简单将自己作为Update操作的一个参数,让观察者知道该去检查哪个目标。
三、代码实现
Observer接口:
public interface Observer {
void update(String state);
void update(Subject subject);
}
这里重载了两个update函数,一个以Subject的变化属性为参数,用以实现
推协议;另一个以Subject本身为参数,用以实现
拉协议。
Subject虚类:
import java.util.*;
public abstract class Subject {
private List<Observer> list=new ArrayList<Observer>();
public void attach(Observer ob)
{
list.add(ob);
System.out.println("Attach an observer");
}
public void detach(Observer ob)
{
list.remove(ob);
System.out.println("Remove an observer");
}
public void notifyObservers(String newstate)
{
for(Observer temp:list)
{
temp.update(newstate);
}
}
public void notifyObservers()
{
for(Observer temp:list)
temp.update(this);
}
}
同样的,notifyObservers也重载了两个,一个以new state为参数;另一个无参数,但是调用observer的update时,将自身传递过去,由Observer决定是否对变化做出响应。
ConcreteObserver类:
public class ConcreteObserver implements Observer{
private String observerState;
private int observerInt;
public void update(String newstate)
{
observerState=newstate;
System.out.println("状态变化,推送:"+observerState);
}
public void update(Subject subject)
{
observerInt=((ConcreteSubject)subject).getNum();
System.out.println("状态变化,拉到:"+observerInt);
}
}
这里要注意以Subject对象为参数的update方法。其中使用了向下造型,这样可以得到实际传递进来的ConcreteSubject的对象,再决定对哪些变化的值做出处理。这就是 拉协议
的关键所在。
ConcreteSubject类:
public class ConcreteSubject extends Subject{
private String state;
private int num;
public String getState()
{
return this.state;
}
public int getNum()
{
return this.num;
}
public void change(String newstate)
{
state=newstate;
System.out.println("状态变化:"+state);
this.notifyObservers(newstate);
}
public void change(String newstate,int num)
{
this.state=newstate;
this.num=num;
System.out.println("状态变化:"+this.state+" "+this.num);
this.notifyObservers();
}
}
注意,change(String,int)改变的是state和num两个值,但是调用了无参数的notifyObservers(),这里被通知Observer只会显示num的改变。
Client类:
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ConcreteSubject subject=new ConcreteSubject();
Observer observer1=new ConcreteObserver();
subject.attach(observer1);
subject.change("I hate You",2);
System.out.println("-----------------");
Observer observer2=new ConcreteObserver();
subject.attach(observer2);
subject.change("I Love you");
}
}
运行结果:
Attach an observer
状态变化:I hate You 2
状态变化,拉到:2
-----------------
Attach an observer
状态变化:I Love you
状态变化,推送:I Love you
状态变化,推送:I Love you