大话设计模式C语言之观察者模式

观察者模式

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

   观察者模式就像是MQTT的Pub/Sub机制,两者道相同,但观察者模式属于一种软件设计模式最重要针对代码层次,而发布订阅机制是一种系统架构模式,针对的是系统架构层次。但本质思想是相同的。

案例代码

观察者接口 observer.h

接口声明了通知接口。在绝大多数情况下,该接口仅包含一个update更新方法。该方法可以拥有多个参数,使发布者能在更新时传递事件的详细信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef OBSERVER_H
#define OBSERVER_H

typedef struct Observer Observer;

/* 观察者回调接口 */
typedef void (*ObserverUpdateFn)(Observer *self, const void *event);

/* 创建 / 销毁 */
Observer *observer_create(ObserverUpdateFn on_update, void *user_data);
void observer_destroy(Observer *obs);

/* 内部使用(Subject 调用) */
void observer_notify(Observer *obs, const void *event);

#endif /* OBSERVER_H */

观察者实现 observer.h

可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口,因此发布者不需要与具体类相耦合。

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
#include <stdlib.h>
#include "observer.h"

struct Observer
{
ObserverUpdateFn on_update;
void *user_data;
};

Observer *observer_create(ObserverUpdateFn on_update, void *user_data)
{
if (!on_update)
{
return NULL;
}

Observer *obs = calloc(1, sizeof(*obs));
if (!obs)
{
return NULL;
}

obs->on_update = on_update;
obs->user_data = user_data;
return obs;
}

void observer_notify(Observer *obs, const void *event)
{
if (!obs || !obs->on_update)
{
return;
}

obs->on_update(obs, event);
}

void observer_destroy(Observer *obs)
{
if (!obs)
{
return;
}

free(obs);
}

主题接口 subject.h

会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef SUBJECT_H
#define SUBJECT_H

typedef struct Subject Subject;
typedef struct Observer Observer;

/* 创建 / 销毁 Subject */
Subject *subject_create(void);
void subject_destroy(Subject *subject);

/* 观察者管理 */
int subject_attach(Subject *subject, Observer *observer);
int subject_detach(Subject *subject, Observer *observer);

/* 状态变更通知 */
void subject_notify(Subject *subject, const void *event);

#endif /* SUBJECT_H */

主题实现 subject.h

当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。

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
#include <stdlib.h>
#include "subject.h"
#include "observer.h"

typedef struct ObserverNode
{
Observer *observer;
struct ObserverNode *next;
} ObserverNode;

struct Subject
{
ObserverNode *head;
};

Subject *subject_create(void)
{
Subject *subject = calloc(1, sizeof(*subject));
if (!subject)
{
return NULL;
}
return subject;
}

int subject_attach(Subject *subject, Observer *observer)
{
if (!subject || !observer)
{
return -1;
}

ObserverNode *node = calloc(1, sizeof(*node));
if (!node)
{
return -1;
}

node->observer = observer;
node->next = subject->head;
subject->head = node;

return 0;
}

int subject_detach(Subject *subject, Observer *observer)
{
if (!subject || !observer)
{
return -1;
}

ObserverNode **curr = &subject->head;
while (*curr)
{
if ((*curr)->observer == observer)
{
ObserverNode *tmp = *curr;
*curr = tmp->next;
free(tmp);
return 0;
}
curr = &(*curr)->next;
}

return -1;
}

void subject_notify(Subject *subject, const void *event)
{
if (!subject)
{
return;
}

ObserverNode *node = subject->head;
while (node)
{
observer_notify(node->observer, event);
node = node->next;
}
}

void subject_destroy(Subject *subject)
{
if (!subject)
{
return;
}

ObserverNode *node = subject->head;
while (node)
{
ObserverNode *next = node->next;
free(node);
node = next;
}

free(subject);
}

测试客户端代码 main.c

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
#include <stdio.h>
#include "subject.h"
#include "observer.h"

/* 具体观察者行为 */
static void logger_update(Observer *self, const void *event)
{
(void)self;
const char *msg = event;
printf("[Logger] receive event: %s\n", msg);
}

static void metrics_update(Observer *self, const void *event)
{
(void)self;
const char *msg = event;
printf("[Metrics] collect data: %s\n", msg);
}

int main(void)
{
Subject *subject = subject_create();

Observer *logger = observer_create(logger_update, NULL);
Observer *metrics = observer_create(metrics_update, NULL);

subject_attach(subject, logger);
subject_attach(subject, metrics);

subject_notify(subject, "SYSTEM_START");
subject_notify(subject, "SYSTEM_RUNNING");

subject_detach(subject, logger);

subject_notify(subject, "SYSTEM_STOP");

observer_destroy(logger);
observer_destroy(metrics);
subject_destroy(subject);

while (1)
;
return 0;
}

总结

   订阅者通常需要一些上下文信息来正确地处理更新。因此发布者通常会将一些上下文数据作为通知方法的参数进行传递。发布者也可将自身作为参数进行传递,使订阅者直接获取所需的数据。当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。