设计模式之观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。(摘自Head First中文版51页)

说明

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某主题对象。该主题对象状态变化时,会通知所有观察者对象,让其能够自动更新。它将观察者和被观察者的对象分离开,提高了应用程序的可维护性和重用性。

观察者模式可分为推模型和拉模型两种方式。

  1. 推模型: 主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
  2. 拉模型:主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。拉模型通常都是把主题对象当做参数传递。

类图

YBRGw9.png

示例

被观察者接口

1
2
3
4
5
6
7
8
9
10
public interface Subject {
//添加观察者
public void addObserver(Observer o);
//删除观察者
public void deleteObserver(Observer o);
//通知所有观察者
public void notifyAllObservers();
//通知指定观察者
public void notifyObserver(Observer o);
}

观察者接口

1
2
3
4
public interface Observer {
//观察者获取观察对象的更新
void update (Subject o, Object arg);
}

具体的被观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ConcreteSubject implements Subject {
//所持有的观察者们
List<Observer> observers = new ArrayList<>();
//Subject 被观察的相关属性
private String state = null;
@Override
public void addObserver(Observer o) {
observers.add(o);//添加到被观察者的 集合中
o.update(this, "观察成功");//通知观察者,观察成功
}
@Override
public void deleteObserver(Observer o) {
observers.remove(o);//删除观察者
o.update(this, "解除观察");//通知观察者接触观察
}
@Override
public void notifyAllObservers() {//通知所有观察者,被观察者的状态
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(this, state);
}

}
@Override
public void notifyObserver(Observer o) {//通知指定观察者 被观察对象的状态
observers.get(observers.indexOf(o)).update(this, state);;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}

具体的观察对象

1
2
3
4
5
6
7
8
9
public class ConcreteObserver1 implements Observer{
//持有的被观察的对象
public ConcreteSubject csub = null;
@Override
public void update(Subject o, Object arg) {
csub = (ConcreteSubject) o;
System.out.println("ConcreteObserver1 获得更新--" + o.getClass().getSimpleName()+ " 发生了 " + arg.toString());
}
}

具体的观察对象2

1
2
3
4
5
6
7
8
public class ConcreteObserver2 implements Observer {
//持有的被观察的对象
public ConcreteSubject csub = null;
@Override
public void update(Subject o, Object arg) {
System.out.println("ConcreteObserver2 获得更新--" + o.getClass().getSimpleName()+ " 发生了 " + arg.toString());
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
ConcreteSubject csub = new ConcreteSubject();
ConcreteObserver1 observer1 = new ConcreteObserver1();
ConcreteObserver2 observer2 = new ConcreteObserver2();
csub.setState("苹果x大降价");
//开启观察
csub.addObserver(observer1);
csub.addObserver(observer2);
//observer1 拉取消息
observer1.csub.notifyObserver(observer1);
//更新状态
csub.setState("MacBook Pro 七折疯抢");
//subject 推消息
csub.notifyAllObservers();
//放弃观察
csub.deleteObserver(observer1);

}

总结

使用场景:

  1. 当一个对象的改变需要同时改变其他对象,并且它不知道具体有多少对象有待改变时,应考虑使用观察者模式;
  2. 一个抽象模型有两个方面,其中一方面依赖与另一方面,这是观察者模式可以将这两者封装在独立的对象中是它们各自独立地改变和复用。
    总的来讲,观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。

优点

观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

缺点

依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。

观察者模式定义了对象之间的一对多关系。主题(可观察者)用一个共同的接口来更新观察者。观察者和被观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。

jdk中有很多观察者模式,java.util.Observable就是其中一种,但是要注意其实现上带来的一些问题。

我们发现Observable是一个“类”而不是一个“接口”,更为糟糕的是其甚至都没有实现一个接口。所以java.util.Observable的实现会产生很多问题,限制了它的使用和复用。我们要使用就必须继承它,如果一个类想同时具有Observable类和另一个超类的行为,则会陷入两难之中,毕竟java不支持多继承。在实际项目中使用观察者模式时,一定要注意这个问题,以免带来不必要的麻烦。