观察者模式
1.定义
观察者模式(Observer)是软件设计模式的一种.在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知.这通常透过呼叫各观察者所提供的方法来实现.此种模式通常被用来实时事件处理系统.
2.认识观察者模式
2.1 目标和观察者之间的关系
按照模式的定义,目标和观察者之间是典型的一对多的关系.
但是要注意,如果观察者只有一个,也是可以的.这样就变相实现了目标和观察者之间一对一的关系,这样就变相实现了目标和观察者之间一对一的关系,这也使得在处理一个对象的状态变化会影响到另一个对象的时候,也可以考虑使用观察者模式.
同样的,一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是update方法的话,这会带来麻烦,因为需要接收多个目标的通知,如果一个update的方法,那就需要在方法内部区分,到底这个更新的通知来自于哪一个目标,不同的目标有不同的后续操作.
一般情况下,观察者应该为不同的观察者目标,定义不同的回调方法,这样实现最简单,不需要在update方法内部进行区分.
2.2 单向依赖
在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而且目标是不会依赖于观察者的.
他们之间联系的主动权掌握在目标手中,只有目标知道什么时候需要通知观察者,在整个过程中,观察者始终是被动的,被动的等待目标的通知,等待目标传值给它.
对目标而言,所有的观察者都是一样的,目标会一视同仁的对待.当然也可以通过在目标里面进行控制,实现有区别对待观察者,比如某些状态变化,只需要通知部分观察者,但那是属于稍微变形的用法了,不属于标准的、原始的观察者模式.
2.3 基本的实现说明
具体的目标实现对象要能维护观察者的注册信息,最简单的实现方案就如同前面的例子那样,采用一个集合来保护观察者的注册信息.
具体的目标实现对象需要维护引起通知的状态,一般情况下是目标自身的状态,变形使用的情况下,也可以是别的对象的状态.
具体的观察者实现对象需要能接收目标的通知,能够接收目标传递的数据,或者是能够主动去获取目标的数据,并进行后续处理.
如果一个观察者观察多个目标,那么在观察者的更新方法里面,需要去判断是来自哪一个目标的通知.一种简单的解决方案就是扩展update方法,比如在方法里面多传递一个参数进行区分等;还有一种更简单的方法,那就是干脆定义不同的回调方法.
3.推模型和拉模型
推模型 目标对象主动想观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或者部分数据,相当于是在广播通信.
拉模型 目标对象在通知观察者的时候,只传递少量信息,如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据.
一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了.
对比 两种实现模型,在开发的时候究竟应该使用哪一种,还是应该具体问题具体分析.
- 推模型是假定目标对象知道观察者需要的数据;而拉模型是目标对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传给观察者,让观察者自己去按需取值.
- 推模型可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定义的,可能无法兼顾美欧考虑到的使用情况.这就意味着出现新情况的时候,就可能需要提供新的update方法,或者是干脆重新实现观察者.
而拉模型就不会造成这样的情况,因为拉模型下,update方法的参数是目标对象本身,这基本上是目标对象能传递的最大数据集合了,基本上可以适应各种情况的需要.
4.实现
观察者模式
- 抽象主题(Subject)角色
- 抽象观察者(Observer)角色
- 具体主题(Concrete Subject)角色
- 具体观察者(Concrete Observer)角色
命名建议
- 观察者模式又被成为发布-订阅模式
- 目标接口的定义,建议在名称后面跟Subject
- 观察者接口定义,建议在名称后面跟Observer
- 观察者接口的更新方法,建议名称为update,当然方法的参数可以根据需要定义,参数个数不限,参数类型不限
抽象主题(Subject)角色:提供一个界面让观察者进行添附与解附作业
import java.util.ArrayList;
import java.util.List;
/**
* 抽象主题(Subject)角色
*/
public abstract class Subject {
private List<Observer> list = new ArrayList<Observer>();
/**
* 新增观察者到串炼内, 以追踪目标对象的变化
* @param observer
*/
public void attach(Observer observer){
list.add(observer);
}
/**
* 将已经存在的观察者从串炼中移除
* @param observer
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 利用观察者所提供的更新函式来通知此目标已经产生变化
*/
public void notifyObserver(){
for (Observer o : list){
o.update();
}
}
}
抽象观察者(Observer)角色:定义了所有观察者都拥有的更新用界面,此界面是用来接收目标类别所发出的更新通知
/**
* 抽象观察者(Observer)角色
*/
public interface Observer {
void update();
}
具体主题(Concrete Subject)角色:提供了观察者欲追踪的状态
/**
* 具体主题(Concrete Subject)角色
*/
public class ConcreteSubject extends Subject{
public void notifyListener(){
this.notifyObserver();
}
}
具体观察者(Concrete Observer)角色:指向目标类别的参考(reference),以接收来自目标类别的更新状态
/**
* 具体观察者(Concrete Observer)角色
*/
public class ConcreteObserver implements Observer{
public void update() {
System.out.println("观察者收到信息,并进行处理.");
}
}
客户端
/**
* 测试使用
*/
public class ObserverClient {
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
concreteSubject.attach(new ConcreteObserver());
concreteSubject.notifyListener();
// stdout
// 观察者收到信息,并进行处理.
}
}
5.总结
优缺点
- 观察者模式实现了观察者和目标之间的抽象耦合
- 观察者模式实现了动态联动
- 观察者模式支持广播通信
- 观察者模式可能会引起无谓的操作
本质
- 触发联动
何时选用
- 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式.
- 如果在更改一个对象的时候,需要同时连带改变其它的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的那一个对象很明显就相当于是目标对象,而需要连带修改的多个其它对象,就作为多个观察者对象了.
- 当一个对象必须通知其它的对象,但是你又希望这个对象和其它被它通知的对象是松散耦合的,也就是说这个对象其实不想知道具体被通知的对象,这种情况可以选用观察者模式,这个对象就相当于是目标对象,而被它通知的对象就是观察者对象了.