0%

设计模式之观察者模式(Observer)

一、动机

观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。

在软件构建过程中,我们需要为某些对象建立一种”通知依赖关系“——一个对象(目标对象)的状态发生改变(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并能形成一定的依赖关系,从而实现软件体系结构的松耦合。

二、解决方案

拥有一些值得关注的状态的对象通常被称为目标,由于它要将自身的状态改变通知给其他对象,我们也将其称为发布者publisher)。所有希望关注发布者状态变化的其他对象被称为订阅者subscribers)。

观察者模式建议你为发布者类添加订阅机制,让每个对象都能订阅或取消订阅发布者事件流。 不要害怕!这并不像听上去那么复杂。实际上,该机制包括1)一个用于存储订阅者对象引用的列表成员变量;2)几个用于添加或删除该列表中订阅者的公有方法。

observer1

现在,无论何时发生了重要的发布者事件,它都要遍历订阅者并调用其对象的特定通知方法。

实际应用中可能会有十几个不同的订阅者类跟踪着同一个发布者类的事件,你不会希望发布者与所有这些类相耦合的。此外如果他人会使用发布者类,那么你甚至可能会对其中的一些类一无所知。

因此,所有订阅者都必须实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。

observer2

使用面向对象的抽象,Observer模式使得可独立的改变目标与观察者,从而使得二者之间的依赖关系达致松耦合。目标发送通知的时候,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。

三、观察者模式的结构

structure_of_Observer

  1. 发布者Publisher)会向其他对象发送值得关注的事件。事件会在发布者自身状态改变或执行特定行为后发生。发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。
  2. 当新事件发生时,发送者会遍历订阅列表并调用每个订阅者对象的通知方法。该方法是在订阅者接口中声明的。
  3. 订阅者Subscriber)接口声明了通知接口。在绝大多数情况下,该接口仅包含一个 update更新方法。该方法可以拥有多个参数,使发布者能在更新时传递事件的详细信息。
  4. 具体订阅者Concrete Subscribers)可以执行一些操作来回应发布者的通知。所有具体订阅者类都实现了同样的接口,因此发布者不需要与具体类相耦合。
  5. 订阅者通常需要一些上下文信息来正确地处理更新。因此,发布者通常会将一些上下文数据作为通知方法的参数进行传递。发布者也可将自身作为参数进行传递,使订阅者直接获取所需的数据。
  6. 客户端Client)会分别创建发布者和订阅者对象,然后为订阅者注册发布者更新。
四、Observer代码示例
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <iostream>
#include <unordered_map>

// 订阅者抽象基类
// 声明了所有具体订阅者同意接受更新信息的接口
class Subscriber {
public:
Subscriber() {}
virtual ~Subscriber() {}
virtual void update() = 0;
};

// 发布者类
// 包含订阅管理代码和通知方法
class Publisher {
private:
// 拥有一个 map,用来存放订阅者 ID 和实际订阅者之间的映射
std::unordered_map<size_t, Subscriber *> subscribers;

public:
Publisher() {}
~Publisher() {}
// 发布者通过此方法通知所有的订阅者
void notifySubscribers() {
std::cout << "Publisher: I'm publisher, updating all subscribers." << std::endl;
for(auto &s : subscribers) {
s.second->update();
}
}
// 发布者提供一个可供订阅者订阅的接口
void subscribe(size_t sid, Subscriber *s) {
subscribers[sid] = s;
}
// 发布者提供一个可供订阅者取消订阅的接口
void unsubscribe(size_t sid) {
subscribers.erase(sid);
}
// 返回目前共有多少个订阅者
void howManyObserver() {
std::cout << "Publisher: There are " << subscribers.size() << " observers." << std::endl;
}
};
// 具体的订阅者类
class concreteSubscriber : public Subscriber {
private:
//拥有一个发布者的引用,用来调用发布者的订阅管理代码
Publisher *m_publisher;
// 订阅者 ID
size_t m_ID;
// 此静态成员变量表示已有订阅者中最大的 ID
// 静态成员,用来更新新注册的订阅者的 ID
static size_t maxID;

public:
concreteSubscriber(Publisher *publisher) : m_publisher(publisher), m_ID(concreteSubscriber::maxID + 1) {
concreteSubscriber::maxID++;
std::cout << "Subscriber: Hello, My ID = " << m_ID << "." << std::endl;
}

~concreteSubscriber() { std::cout << "Subscriber: Goodbye, My ID = " << m_ID << "." << std::endl; }

// 发布者通过调用此方法来通知每个订阅者
void update() override {
std::cout << "Subscriber: My ID = " << m_ID << ", I'm updated." << std::endl;
}

void subscribe() {
std::cout << "Subscriber: My ID = " << m_ID << ", I'm subscribed." << std::endl;
m_publisher->subscribe(m_ID, this);
}

void unsubscribe() {
std::cout << "Subscriber: My ID = " << m_ID << ", I'm unsubscribed." << std::endl;
m_publisher->unsubscribe(m_ID);
}
};

size_t concreteSubscriber::maxID = 0;

// 客户端程序可在运行时配置发布者和订阅者
void clientCode() {
Publisher *publisher = new Publisher;

concreteSubscriber *subscriber1 = new concreteSubscriber(publisher);
concreteSubscriber *subscriber2 = new concreteSubscriber(publisher);
concreteSubscriber *subscriber3 = new concreteSubscriber(publisher);

subscriber1->subscribe();
subscriber2->subscribe();
subscriber3->subscribe();

publisher->notifySubscribers();

subscriber2->unsubscribe();

publisher->notifySubscribers();

delete subscriber1;
delete subscriber2;
delete subscriber3;
delete publisher;
}

int main() {
clientCode();
}

Execution result:
Subscriber: Hello, My ID = 1.
Subscriber: Hello, My ID = 2.
Subscriber: Hello, My ID = 3.
Subscriber: My ID = 1, I'm subscribed.
Subscriber: My ID = 2, I'm subscribed.
Subscriber: My ID = 3, I'm subscribed.
Publisher: There are 3 observers.
Publisher: I'm publisher, updating all subscribers.
Subscriber: My ID = 3, I'm updated.
Subscriber: My ID = 1, I'm updated.
Subscriber: My ID = 2, I'm updated.
Subscriber: My ID = 2, I'm unsubscribed.
Publisher: There are 2 observers.
Publisher: I'm publisher, updating all subscribers.
Subscriber: My ID = 3, I'm updated.
Subscriber: My ID = 1, I'm updated.
Subscriber: Goodbye, My ID = 1.
Subscriber: Goodbye, My ID = 2.
Subscriber: Goodbye, My ID = 3.
五、使用场景
  • 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。

当你使用图形用户界面类时通常会遇到一个问题。比如,你创建了自定义按钮类并允许客户端在按钮中注入自定义代码,这样当用户按下按钮时就会触发这些代码。观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。你可在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码。

  • 当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。

    订阅列表是动态的, 因此订阅者可随时加入或离开该列表。

六、优缺点
  • 开闭原则。你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。
  • 你可以在运行时建立对象之间的联系。
  • 订阅者的通知顺序是随机的。
七、与其它模式的关系
  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 中介者观察者之间的区别往往很难记住。在大部分情况下,你可以使用其中一种模式, 而有时可以同时使用。让我们来看看如何做到这一点。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者非常相似。

    当你感到疑惑时,记住可以采用其他方式来实现中介者。例如,你可永久性地将所有组件链接到同一个中介者对象。这种实现方式和观察者并不相同,但这仍是一种中介者模式

    假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。