定义
考虑这样一个场景,气象中心会隔一段时间更新一次气象数据,比如气温,气压,前端展示的页面要能够发现气象数据更新之后就获取到新数据并展示,比较一般的办法就是实现几个不同的类,比如展示气温,气压之类的,然后一有更新就调用每个类的更新方法来更新,这样的办法很不利于扩展,比如说我要添加一个展示相关的类,我就需要在气象数据的相关类中做修改,这样让两个类之间紧紧的关联在一起,也就是常说的紧耦合了,很不利于当新需求过来时进行扩展;
当然观察者模式就是用来解决类似的问题的,它利用一个主题来表示数据发布更新的源头,用多个实现自一个接口的观察者来负责获取更新数据并处理的类;我们只需要针对当前主题存储它在每次更新时需要通知的观察者信息(同一接口);在数据发生更新时,只需要遍历存储的数据并调用当前观察者类的更新方法即可让观察者获取更新数据,也可以在观察者不想获取新数据时进行注销等操作,这里为什么解决了上述问题,也就是解耦合?因为我们不需要知道每个观察者实现的具体类,只需要让他们继承自同一接口,每次更新时调用接口的更新方法即可,具体怎么更新由每个观察者自己来实现.
UML图
实现
主题接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16interface Subject{
/**
* 注册观察者
* @param observer
* @return
*/
public boolean registerObserver(Observer observer);
/**
* 移除观察者
* @param observer
* @return
*/
public boolean removeObserver(Observer observer);
int notifyObserver();
}实际主题:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52class Subject_a implements Subject{
// 观察者的映射
private Map<String ,Observer> observers = new HashMap<>();
private String subjectName;
// 数据
private int a, b, c;
Subject_a(String subjectName){
this.subjectName = subjectName;
}
public boolean registerObserver(Observer observer) {
if (observers.get(observer.name()) != null){
System.out.println(observer.name()+"已被其他观察者占用,请重新注册!");
return false;
}
System.out.println(subjectName+"注册"+observer.name());
observers.put(observer.name(),observer);
return true;
}
public boolean removeObserver(Observer observer) {
if (observers.get(observer.name()) == null){
System.out.println("观察者"+observer.name()+"不存在,请重新注销!");
return false;
}
System.out.println(subjectName+"移除"+observer.name());
observers.remove(observer.name());
return true;
}
/**
* 通知观察者
* @return
*/
public int notifyObserver() {
System.out.println(subjectName+"发布更新,更新数据为 a:"+a+" b:"+b+" c:"+c);
for (Map.Entry<String ,Observer> entry : observers.entrySet()){
entry.getValue().update(subjectName,a,b,c);
}
return observers.size();
}
public void dataChanged(int a, int b, int c){
this.a = a;
this.b = b;
this.c = c;
int sum = notifyObserver();
System.out.println("接收到"+subjectName+"的人数:"+sum);
}
}观察者接口:
1
2
3
4
5
6
7
8
9
10
11interface Observer{
/**
* 更新数据
* @param subjectName
* @param a
* @param b
* @param c
*/
void update(String subjectName, int a, int b, int c);
String name();
}实际观察者:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class ObserverA implements Observer{
String observerName;
public String getObserverName() {
return observerName;
}
ObserverA(String observerName) {
this.observerName = observerName;
}
public void update(String subjectName,int a, int b, int c) {
System.out.println(observerName+"收到来 自"+subjectName+"的更新,更新数据为 a:"+a+" b:"+b+" c:"+c);
}
public String name() {
return observerName;
}
}写个代码测试一下:
1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
Subject_a subject_a = new Subject_a("主题1");
ObserverA observerA = new ObserverA("观察者1");
ObserverA observerA1 = new ObserverA("观察者2");
subject_a.registerObserver(observerA);
subject_a.registerObserver(observerA1);
subject_a.dataChanged(1,2,3);
subject_a.removeObserver(observerA);
subject_a.dataChanged(3,4,5);
}这里我们只用了一个主题,可以实现多个主题,来注册不同的观察者,只需要实现接口即可;我们在主题1上注册了两个观察者1,2,并更新一次数据,观察者1,2都会接到数据并更新;接下来移除观察者1,再更新一次数据,会发现观察者1接收不到新的数据了.打印一下输出:
谈一谈优缺点:
优点:最重要的一点,观察者模式将主题和观察者解耦合,主题不需要了解观察者内部的实现即可传输数据过去;一对多,一个主题每次更新数据时可以传输给多个观察者,观察者想要接收更新只需要调用注册方法,想要不接收更新只需要调用注销方法,扩展起来很方便.
缺点:由于主题不知道观察者内部的实现,所以主题每次都会把所有更新的数据传给观察者,有可能某一观察者只需要一点数据却接受了全部,一浪费资源,二有可能会产生安全相关的问题;其次每次都是主题数据更新时立马传给观察者,没有考虑有可能此时观察者正在高负载工作,频繁地通知观察者会让观察者接收频率太快,没有观察者自己想获取数据时请求更新更考虑到了性能的问题.