在构建复杂系统时,对象间的依赖关系往往会变得错综复杂,直接导致代码的可维护性和可扩展性下降。设计模式中的观察者模式(Observer)正是解决这类问题的利器。通过引入观察者模式,我们可以实现主题(Subject)与观察者(Observer)之间的松耦合,让主题状态的改变自动通知到所有相关的观察者,从而构建出更灵活、更易于维护的系统。
场景重现:告警系统设计
假设我们需要设计一个告警系统。这个系统需要监控各种指标,例如 CPU 利用率、内存使用率、磁盘空间等。当某个指标超过预设的阈值时,系统需要发出告警。告警的方式可能有很多种,例如发送邮件、短信、微信通知等。如果我们将告警逻辑直接写在监控代码中,那么每次新增一种告警方式,都需要修改监控代码,这显然是不可取的。而且告警的粒度可能也会不同,例如某些告警只需要发送给运维人员,而某些告警需要发送给开发人员。
观察者模式:底层原理剖析
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生改变时,所有依附于它的观察者对象都会收到通知并更新自己。它主要包含以下几个角色:
- 主题(Subject): 维护一个观察者列表,提供添加、删除观察者的方法,并在状态改变时通知所有观察者。
- 观察者(Observer): 定义一个更新接口,当收到主题的通知时,执行相应的更新操作。
- 具体主题(ConcreteSubject): 主题的具体实现,负责维护自身的状态,并在状态改变时通知所有观察者。
- 具体观察者(ConcreteObserver): 观察者的具体实现,实现更新接口,根据主题的状态进行相应的处理。
C++ 代码实现:告警系统示例
下面是一个使用 C++ 实现的告警系统示例:
#include <iostream>
#include <vector>
#include <string>
// 观察者接口
class Observer {
public:
virtual void update(const std::string& message) = 0;
};
// 主题类
class Subject {
private:
std::vector<Observer*> observers;
public:
void attach(Observer* observer) {
observers.push_back(observer);
}
void detach(Observer* observer) {
for (auto it = observers.begin(); it != observers.end(); ++it) {
if (*it == observer) {
observers.erase(it);
break;
}
}
}
void notify(const std::string& message) {
for (Observer* observer : observers) {
observer->update(message);
}
}
};
// 具体观察者:邮件告警
class EmailAlert : public Observer {
private:
std::string email;
public:
EmailAlert(const std::string& email) : email(email) {}
void update(const std::string& message) override {
std::cout << "Sending email to " << email << ": " << message << std::endl;
}
};
// 具体观察者:短信告警
class SMSAlert : public Observer {
private:
std::string phone;
public:
SMSAlert(const std::string& phone) : phone(phone) {}
void update(const std::string& message) override {
std::cout << "Sending SMS to " << phone << ": " << message << std::endl;
}
};
// 具体主题:监控系统
class MonitorSystem : public Subject {
private:
int cpuUsage;
public:
void setCpuUsage(int usage) {
cpuUsage = usage;
if (cpuUsage > 90) {
notify("CPU usage is over 90%!");
}
}
};
int main() {
MonitorSystem monitor;
EmailAlert emailAlert("admin@example.com");
SMSAlert smsAlert("13800000000");
monitor.attach(&emailAlert);
monitor.attach(&smsAlert);
monitor.setCpuUsage(95);
monitor.detach(&smsAlert);
monitor.setCpuUsage(80); // 不会发送短信
return 0;
}
实战避坑:线程安全与内存管理
在使用观察者模式时,需要注意以下几点:
- 线程安全: 如果主题和观察者运行在不同的线程中,需要考虑线程安全问题。可以使用互斥锁(
std::mutex)来保护观察者列表,避免并发访问导致的数据竞争。例如,在使用 Nginx 反向代理时,如果 upstream server 的状态变化需要通知多个 worker 进程,就需要考虑进程间通信的线程安全问题。 - 内存管理: 观察者模式中,主题维护着观察者列表,需要注意观察者的生命周期。如果观察者对象被销毁,但主题仍然持有指向它的指针,就会导致悬挂指针(dangling pointer)。可以使用智能指针(
std::shared_ptr、std::weak_ptr)来管理观察者的生命周期,避免内存泄漏。 - 循环依赖: 需要避免主题和观察者之间出现循环依赖。例如,主题通知观察者更新,观察者又反过来修改主题的状态,导致无限循环。
观察者模式与消息队列
观察者模式和消息队列(如 Kafka、RabbitMQ)都可以实现异步通信,但它们的应用场景有所不同。观察者模式适用于进程内部或单机环境下的对象间通信,而消息队列适用于分布式系统中的服务间通信。例如,在使用宝塔面板搭建网站时,如果需要监控网站的访问量,可以使用观察者模式将访问量数据发送到监控模块;如果需要将访问量数据发送到远程服务器进行分析,则可以使用消息队列。
总而言之,C++ 设计模式中的观察者模式是一种非常有用的设计模式,可以有效地降低对象之间的耦合度,提高系统的可维护性和可扩展性。在实际开发中,可以根据具体的场景选择合适的方式来实现观察者模式,例如可以使用标准库中的 std::function 和 std::bind 来简化观察者的实现。例如,可以使用 Lambda 表达式来实现简单的观察者。同时,也要注意线程安全和内存管理等问题,避免引入新的 Bug。
观察者模式的优势总结
- 松耦合: 主题和观察者之间不需要知道彼此的具体实现,只需要知道彼此的接口即可。
- 可扩展性: 可以方便地新增或删除观察者,而不需要修改主题的代码。
- 可重用性: 主题和观察者可以独立地进行测试和重用。
冠军资讯
夜雨听风