行为型设计模式的一种

【DP】定义

定义了一种一对多的依赖关系,让多个观察者对象(Observer)同时监听某一个主题对象(Subject)。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

使用场景

  1. 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  2. 当一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  3. 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

类图

观察者模式

类图核心是:动作主体的三个方法attach、detach和notify方法,以及动作主体对观察者的使用。观察者类以接口的形式注册到动作主体,使用attach和detach进行观察者的添加和移除,notify方法通过调用各个观察者的update方法,起到通知的作用。

类图中绿色依赖关系表示可有可无,当观察者需要动作主体的状态等信息时可以依赖具体的动作主体类。

举例

假设当程序出错时需要分别向本地日志、远程日志和控制台上输出错误信息,并且输出位置以后还可能有变化,那么使用观察者模式就非常合适了。设计如下:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* 动作主体接口
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notify(String msg);
}
/**
* 动作主体类
*/
public class SubjectImpl implements Subject {
private List<Observer> observers;

@Override
public void attach(Observer observer) {
observers.add(observer);
}

@Override
public void detach(Observer observer) {
observers.remove(observer);
}

@Override
public void notify(String msg) {
for (Observer observer : observers) {
observer.writeLog(msg);
}
}
}

/**
* 观察者接口
*/
public interface Observer {
void writeLog(String msg);
}
/**
* 观察者实现类1---写本地日志
*/
public class LocalLogObserver implements Observer {
@Override
public void writeLog(String msg) {
// write local log
}
}
/**
* 观察者实现类2---写日志服务器上的日志
*/
public class RemoteLogObserver implements Observer {
@Override
public void writeLog(String msg) {
// write remote log
}
}
/**
* 观察者实现类3---写控制台
*/
public class ConsoleLogObserver implements Observer {
@Override
public void writeLog(String msg) {
// print on console
}
}

/**
* 客户端调用
*/
public class Main {
public static void main() {
// 声明动作主体
Subject subject = new SubjectImpl();
// 注册观察者
subject.attach(new LocalLogObserver());
subject.attach(new RemoteLogObserver());
subject.attach(new ConsoleLogObserver());

try {
// 一段可能抛异常的代码
} catch (Exception e) {
String msg = e.getMessage();
// 调用通知代码
subject.notify(msg);
}
}
}

当有其他的日志打印需求时,可以很方便的注册并在客户端透明的使用。

注意事项

  1. 当观察者不需要动作主体的状态信息时,观察者完全不依赖动作主体,保证了低耦合。