今天有同事问我关于Observer模式的一个问题,说观察者(Observer)为什么要依赖于主题(Subject),如下图所示:
从上图可以看出,具体的观察者对具体的主题有一个依赖, 而且从JDK本身提供的Observer接口,我们也可以看到,确实对主题(在JDK的实现中,为Observable接口)有一个依赖,如代码所示:
public interface Observer {
void update(Observable o, Object arg);
}
这个是为什么呢,为什么这里会依赖主题对象?其实这个跟Subject与Observer之间通讯的方式有关,当Subject本身状态发生变化时,会去通知注册了的Observer(即调用每个observer的update()方法),那么这个时候,主题本身要怎么样去给Observer传输其需要的数据呢?
在我们看到的大多数观察者的实现中,主题(Subject)在通知注册的Observer时,都会把Observer所需要的数据封装好,传给Observer,这个也就是所谓的‘推’的模式,主题主动将数据推给观察者,这种情况下,Observer的接口往往定义如下:
public interface Observer {
void update(Object arg);//从主题传入的数据
}
在这种‘推’的模式下,观察者本身是不依赖于主题对象的。 但还有另外一种所谓‘拉’的通讯方式,是指观察者在需要数据的时候主动从主题对象中获取,这个情况下面就会出现观察者依赖于主题对象,
public interface Observer {
void update(Subject subject);
}
由于这种拉的实现方法出现的比较少,而且‘拉’数据的模式有一个比较大的缺点,那就是出现了主题对象和观察者对象之间的循环依赖,处理不好则很容易出来死循环。
但是对于一个完整的观察者模式来说,这两种数据传输的方式都是需要的,这也就是JDK的Observer接口中的update()方法要有两个参数的原因(Obervable对象一般对应于拉模式,Object对象一般对应于推模式),如果你做过Swing编程,你会发现在Swing的事件处理中,listner(实际上就是Observer)所接受的参数也支持推拉两种数据方式,如
public interface MouseListener extends EventListener {
public void mouseClicked(MouseEvent e);
。。。其他略
}
这里的MouseEvent对象实际上也包含了数据来源对象(触发事件的对象),即具体主题对象,而除了主题对象之外的其他属性,我们都可以看成是推模式中所传的数据。
好了,解决了同事了疑问,还需要点明Observer模式的另一职责。由于我们大多数的Observer模式的实现都很简单,在这样的实现下,主题对象大多只拥有一个职责,那就是管理Observer的职责(包括通知Observer),
class ConcreteSubject implement ISubject
{
private List observers=..
public void addObserver(Observer obs)
{
//add observer
}
public void removeObserver(Observer obs)
{
//remove observer
}
public void notifyObservers(Object obj)
{
//notify observers
}
}
加上Observer模式是为了解决一对多的关系,久而久之,导致大多数人都忘记了主题对象(Subject)本身还应该有另一个职责,管理Observer只是主题(Subject)对象应有的共同的职责,不要忘了,还有多主题对象这么一回事。举个以前看到的例子,
假设我们的主题对象需要从远程获取一些数据,并分别的将其记录在DB中,和显示在Screen上,那么套用Observer模式,可以表示为:
其中,DBObserver将拿到的数据写到DB中,而DisplayObserver将拿到的数据显示在SCREEN上,而MessagesSubject则有了两个职责,一个是我们前面说的管理Observer的职责,另一个是去远程取数据的职责,而这个我认为才是主题对象(Subject)应该有的具体的职责。
public class MessagesSubject extends AbstractSubject implements Runnable
{
//管理Observer的职责会从AbstractSuject中获得
//真正的业务逻辑
public void run()
{
//从远程获得messages
//通知观察者
}
完成一个完整的Observer模式很难,考虑的东西比较多(光是通知Observer这部分就有几种不同的实现方式),不推荐每次都需要实现一个很完整的Observer模式,但是我们不应该遗忘这些构成完整Observer模式锁需要的部分。