往期地址:

  • 设计模式(一)——简单工厂模式
  • 设计模式(二)——策略模式
  • 设计模式(三)——装饰模式

本期主题:

使用c和c++代码,讲解观察者模式、发布订阅模式


发布-订阅模式

  • 1.什么是发布-订阅模式
  • 2.实例
    • 2.1 场景
    • 2.2 代码设计
    • 2.3 代码的优化
  • 3.用C语言来实现

1.什么是发布-订阅模式

  • 发布-订阅模式是一种行为设计模式,它允许多个对象通过事件的发布和订阅来进行通信;
  • 在这种模式中,发布者(又称为主题)负责发布事件,而订阅者(也称为观察者)则通过订阅主题来接收这些事件;
  • 这种模式使得应用程序的不同部分能够松散耦合,并且可以动态地添加或删除订阅者;

2.实例

2.1 场景

A、B、C、D 4个人都订阅了报纸,所以希望当有新报纸来时,通知到这4个人,并且订阅的人可能后面会越来越多,也可能减少,需要有一个机制能够方便的添加、删除订阅的人。因此需求如下:

  1. 当有新报纸时,需要通知到所有订阅的人;
  2. 订阅的人可以方便的添加、修改、删除;

2.2 代码设计

设计思路:
发布者(也称为主题),负责:

  • 发布事件,把消息推送到所有的订阅者
  • 负责管理(增加、删除)订阅者

订阅者负责:

  • 具体的发布消息的实现

示例代码:

#include #include #include using namespace std;// 前向声明class Publisher;class Subscriber;// 订阅者接口class Subscriber {public:virtual void update(const string& message) = 0;};// 发布者类class Publisher {public:// 添加订阅者void subscribe(Subscriber* subscriber) {subscribers_.push_back(subscriber);}// 删除订阅者void unsubscribe(Subscriber* subscriber) {for (auto it = subscribers_.begin(); it != subscribers_.end(); ++it) {if (*it == subscriber) {subscribers_.erase(it);break;}}}// 发送消息给所有订阅者void notify(const string& message) {for (auto subscriber : subscribers_) {subscriber->update(message);}}private:vector<Subscriber*> subscribers_;};// 订阅者1class Subscriber1 : public Subscriber {public:virtual void update(const string& message) {cout << "Subscriber1 received message: " << message << endl;}};// 订阅者2class Subscriber2 : public Subscriber {public:virtual void update(const string& message) {cout << "Subscriber2 received message: " << message << endl;}};int main() {Publisher publisher;Subscriber1 subscriber1;Subscriber2 subscriber2;// 添加订阅者publisher.subscribe(&subscriber1);publisher.subscribe(&subscriber2);// 发送消息publisher.notify("Hello, world!");// 删除订阅者publisher.unsubscribe(&subscriber1);// 发送另一条消息publisher.notify("Goodbye!");return 0;}//测试结果:$ ./a.outSubscriber1 received message: Hello, world!Subscriber2 received message: Hello, world!Subscriber2 received message: Goodbye!

2.3 代码的优化

有些场景是我们并不想一有消息就通知所有的订阅者,我们希望在添加订阅者时,就知道这个订阅者需要订阅哪种消息,这样无关的消息就不会推送给他 ,因此我们对代码进行修改,在发布者类中添加了一个Map,来进行事件的管理:

#include #include #include #include using namespace std;// 前向声明class Publisher;class Subscriber;// 订阅者接口class Subscriber {public:virtual void update(const string& topic, const string& message) = 0;};// 发布者类class Publisher {public:// 添加订阅者void subscribe(const string& topic, Subscriber* subscriber) {subscribers_[topic].push_back(subscriber);}// 删除订阅者void unsubscribe(const string& topic, Subscriber* subscriber) {auto it = subscribers_.find(topic);if (it != subscribers_.end()) {auto& subscribers = it->second;for (auto it2 = subscribers.begin(); it2 != subscribers.end(); ++it2) {if (*it2 == subscriber) {subscribers.erase(it2);break;}}}}// 发送消息给所有订阅者void notify(const string& topic, const string& message) {auto it = subscribers_.find(topic);if (it != subscribers_.end()) {auto& subscribers = it->second;for (auto subscriber : subscribers) {subscriber->update(topic, message);}}}private:unordered_map<string, vector<Subscriber*>> subscribers_;};// 订阅者1class Subscriber1 : public Subscriber {public:virtual void update(const string& topic, const string& message) {cout << "Subscriber1 received message: " << message << " on topic: " << topic << endl;}};// 订阅者2class Subscriber2 : public Subscriber {public:virtual void update(const string& topic, const string& message) {cout << "Subscriber2 received message: " << message << " on topic: " << topic << endl;}};int main() {Publisher publisher;Subscriber1 subscriber1;Subscriber2 subscriber2;// 添加订阅者publisher.subscribe("topic1", &subscriber1);publisher.subscribe("topic2", &subscriber2);// 发送消息publisher.notify("topic1", "Hello, world!");// 删除订阅者publisher.unsubscribe("topic1", &subscriber1);// 发送另一条消息publisher.notify("topic1", "Goodbye!");publisher.notify("topic2", "Hello, again!");return 0;}//测试结果:$ ./a.outSubscriber1 received message: Hello, world! on topic: topic1Subscriber2 received message: Hello, again! on topic: topic2

3.用C语言来实现

我们采用了链表来管理订阅者。

  • 每个事件类型对应一个订阅者链表,每个链表节点包含了一个回调函数和指向下一个节点的指针;
  • 在订阅和取消订阅时,我们需要遍历对应的链表,找到要添加或删除的节点,订阅时添加上对应的事件和回调处理;
  • 在通知时,通知事件和具体的消息;
#include #include #include #define MAX_EVENT_TYPE 10#define MAX_EVENT_SIZE 100typedef enum {EVENT_TYPE_1,EVENT_TYPE_2,EVENT_TYPE_3,EVENT_TYPE_4,EVENT_TYPE_5,EVENT_TYPE_6,EVENT_TYPE_7,EVENT_TYPE_8,EVENT_TYPE_9,EVENT_TYPE_10,EVENT_TYPE_MAX} EventType;typedef struct {char data[MAX_EVENT_SIZE];} Event;typedef struct SubscriberNode {void (*callback)(Event *);struct SubscriberNode *next;} SubscriberNode;typedef struct {SubscriberNode *head;SubscriberNode *tail;} SubscriberList;SubscriberList subscriber_list[EVENT_TYPE_MAX] = {{NULL, NULL}};void publisher_subscribe(EventType event_type, void (*callback)(Event *)) {SubscriberNode *node = (SubscriberNode *)malloc(sizeof(SubscriberNode));node->callback = callback;node->next = NULL;SubscriberList *list = &subscriber_list[event_type];if (!list->head) {list->head = list->tail = node;return;}list->tail->next = node;list->tail = node;}void publisher_unsubscribe(EventType event_type, void (*callback)(Event *)) {SubscriberList *list = &subscriber_list[event_type];if (!list->head) {return;}SubscriberNode *cur_node = list->head;SubscriberNode *prev_node = NULL;while (cur_node) {if (cur_node->callback == callback) {if (prev_node) {prev_node->next = cur_node->next;} else {list->head = cur_node->next;}if (!cur_node->next) {list->tail = prev_node;}free(cur_node);return;}prev_node = cur_node;cur_node = cur_node->next;}}void publisher_notify(EventType event_type, Event *event) {SubscriberList *list = &subscriber_list[event_type];SubscriberNode *cur_node = list->head;while (cur_node) {cur_node->callback(event);cur_node = cur_node->next;}}void subscriber_callback1(Event *event) {printf("Subscriber 1 received event: %s\n", event->data);}void subscriber_callback2(Event *event) {printf("Subscriber 2 received event: %s\n", event->data);}int main() {Event event1 = {"Event 1 data"};Event event2 = {"Event 2 data"};publisher_subscribe(EVENT_TYPE_1, subscriber_callback1);publisher_subscribe(EVENT_TYPE_1, subscriber_callback2);publisher_notify(EVENT_TYPE_1, &event1);printf("\n");publisher_unsubscribe(EVENT_TYPE_1, subscriber_callback2);publisher_notify(EVENT_TYPE_1, &event2);return 0;}//测试结果:$ ./a.outSubscriber 1 received event: Event 1 dataSubscriber 2 received event: Event 1 dataSubscriber 1 received event: Event 2 data