往期地址:


本期主题:

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



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

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

2.实例

2.1 场景

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

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

2.2 代码设计

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

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

订阅者负责:

  • 具体的发布消息的实现

示例代码:

#include <iostream>
#include <vector>
#include <string>

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_;
};

// 订阅者1
class Subscriber1 : public Subscriber {
public:
    virtual void update(const string& message) {
        cout << "Subscriber1 received message: " << message << endl;
    }
};

// 订阅者2
class 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.out
Subscriber1 received message: Hello, world!
Subscriber2 received message: Hello, world!
Subscriber2 received message: Goodbye!


2.3 代码的优化

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

#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>

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_;
};

// 订阅者1
class Subscriber1 : public Subscriber {
public:
    virtual void update(const string& topic, const string& message) {
        cout << "Subscriber1 received message: " << message << " on topic: " << topic << endl;
    }
};

// 订阅者2
class 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.out
Subscriber1 received message: Hello, world! on topic: topic1
Subscriber2 received message: Hello, again! on topic: topic2

3.用C语言来实现

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

  • 每个事件类型对应一个订阅者链表,每个链表节点包含了一个回调函数和指向下一个节点的指针;
  • 在订阅和取消订阅时,我们需要遍历对应的链表,找到要添加或删除的节点,订阅时添加上对应的事件和回调处理;
  • 在通知时,通知事件和具体的消息;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_EVENT_TYPE 10
#define MAX_EVENT_SIZE 100

typedef 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.out
Subscriber 1 received event: Event 1 data
Subscriber 2 received event: Event 1 data

Subscriber 1 received event: Event 2 data

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐