JUNIT源码探秘(五):Junit代码分析之观察者模式
JUnit源码中实现支持不同的使用方式:swt、swing的UI方式和控制台方式,对于这些不同的UI如何提供统一的接口供它们获取测试过程的信息(比如出现的异常信息,测试成功,测试失败的代码行数等等)?我们试想一下这个场景,当一个error或者fail产生的时候,测试能够马上通知这些UI客户端:发生错误了,发生了什么错误,错误是什么等等。显而易见,这是一个订阅-发布或者源-监听机制应用的场景,应当使用观察者模式。那么什么是观察者模式呢?
为了便于理解观察者模式,举一个现实生活中的例子:在中国好声音比赛过程其实是观察者模式的好体现,可以这样说吴莫愁等上台比赛者是一个个被观察者,而刘欢、那英、杨坤等人是4个观察者,被观察者操作(唱歌)时,观察者们开始操作(评分),被观察者唱歌是通知观察者们进行评分。
另外在第二季传刘欢等人不在作为导师,当然其中的歌手也会变化。由上面的例子可以产出,在观察者模式中的角色主要有两大类四个角色即:观察者、被观察者两大类,四个角色即:观察者接口类(导师)、观察者具体实现类(具体导师,比如刘欢等)、被观察者接口类(歌手或者演员)、具体被观察者(吴莫愁等人)
根据以上的例子总结一下观察者模式的组成角色:
抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类或接口来实现。这个映射到上面的例子是被观察者接口类(歌手或者演员)
抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。 这个映射到上面的例子中的角色自然是观察者接口类(导师)
具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。这个映射到上面的例子中的角色自然是被观察者的实现类(即吴莫愁等人)
具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。通常用一个子类实现这个映射到上面的例子中的角色自然是观察者具体实现类(具体导师,比如刘欢等)
再看看他们之间的关联关系
Watcher及其子类维护一个观察者列表,当需要通知所有的Watched对象时调用notifyWatchers方法遍历Watched集合,并调用它们的update方法更新。而具体的观察者实现Watched接口(或者抽象类),提供具体的更新行为。
具体实现代码如下:
抽象主题角色:
public interface Watched {
public void addWatcher(Watcher wather);
public void delWatcher(Watcher wather);
public void notifyWatchers(String str);
}
具体主题角色:
public class WatchedImpl implements Watched {
List list = newArrayList();
public void addWatcher(Watcher wather) {
list.add(wather);
}
public void delWatcher(Watcher wather) {
list.remove(wather);
}
public void notifyWatchers(String str) {
for (Watcher wather : list) {
wather.update(str);
}
}
抽象观察者角色:
public interface Watcher {
public void update(String str);
}
具体观察者角色:
public class WatcherImpl implements Watcher{
public void update(String str) {
System.out.print(str);
}
}
观察者模式会达到如下效果:
1) 目标和观察者的抽象耦合,目标仅仅与抽象层次的简单接口Watcher松耦合,而没有与具体的观察者紧耦合
2) 支持广播通信
3) 缺点是可能导致意外的更新,因为一个观察者并不知道其他观察者,它的更新行为也许将导致一连串不可预测的更新的行为
接着看下JUnit是怎么实现这个模式的。在junit.framework包中我们看到了一个Watcher观察者接口——TestListener,代码如下:
public interfaceTestListener {
public void addError(Test test, Throwable t);
public void addFailure(Test test, AssertionFailedError t);
public void endTest(Test test);
public void startTest(Test test);
}