C++ 设计模式——观察者模式

观察者模式

观察者模式

观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

引入"观察者"设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。

主要组成部分

  1. 主题(Subject)

    • 也叫观察目标,指被观察的对象。
    • 维护观察者列表,提供添加、删除观察者的方法。
    • 当状态变化时,通知所有的观察者。
  2. 观察者(Observer)

    • 定义一个更新接口,以便主题在状态变化时调用。
    • 每个观察者都可以根据主题的变化更新自身状态。
  3. 具体主题(ConcreteSubject)

    • 实现主题的接口,维护具体的状态。
    • 在状态变化时,调用通知方法来更新所有观察者。
  4. 具体观察者(ConcreteObserver)

    • 实现观察者接口,更新自身状态以反映主题的变化。
    • 每个观察者可以根据主题的状态执行特定的操作。

例一:工作流程

  1. 观察者注册到主题 :在主函数中,使用 attach 方法将观察者注册到主题。
  2. 主题的状态发生变化 :调用 setState 方法改变主题的状态。
  3. 主题调用通知方法 :在 setState 方法中,调用 notify 方法通知所有注册的观察者。
  4. 观察者接收到通知后,更新自身状态 :每个观察者实现 update 方法,接收到通知后更新自身状态并输出。
第一步:定义观察者接口

首先,定义一个观察者接口,所有观察者都需要实现这个接口。

cpp 复制代码
// 观察者接口
class Observer {
public:
    virtual void update(int state) = 0; // 更新接口
};
第二步:定义主题接口

接下来,定义一个主题接口,主题需要维护观察者列表,并提供添加、删除观察者的方法。

cpp 复制代码
// 主题接口
class Subject {
public:
    virtual void attach(Observer* observer) = 0; // 添加观察者
    virtual void detach(Observer* observer) = 0; // 移除观察者
    virtual void notify() = 0; // 通知观察者
};
第三步:实现具体主题

实现一个具体主题类,维护观察者列表和状态,并在状态变化时通知观察者。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove

// 具体主题
class ConcreteSubject : public Subject {
private:
    std::vector<Observer*> observers; // 观察者列表
    int state; // 主题的状态

public:
    void attach(Observer* observer) override {
        observers.push_back(observer); // 添加观察者
    }

    void detach(Observer* observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者
    }

    void notify() override {
        for (Observer* observer : observers) {
            observer->update(state); // 通知所有观察者
        }
    }

    void setState(int newState) {
        state = newState; // 更新状态
        notify(); // 状态变化时通知观察者
    }

    int getState() const {
        return state; // 获取当前状态
    }
};
第四步:实现具体观察者

实现观察者类,更新自身状态以反映主题的变化。

cpp 复制代码
// 具体观察者
class ConcreteObserver : public Observer {
private:
    std::string name; // 观察者名称
    int observedState; // 观察者的状态

public:
    ConcreteObserver(const std::string& name) : name(name) {}

    void update(int state) override {
        observedState = state; // 更新观察者的状态
        std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;
    }
};
第五步:主函数

在主函数中,创建主题和观察者对象,注册观察者,并演示状态变化。

cpp 复制代码
// 主函数
int main() {
    ConcreteSubject subject; // 创建具体主题

    ConcreteObserver observer1("Observer 1"); // 创建观察者1
    ConcreteObserver observer2("Observer 2"); // 创建观察者2

    subject.attach(&observer1); // 注册观察者1
    subject.attach(&observer2); // 注册观察者2

    subject.setState(1);  // 状态变化,通知所有观察者
    subject.setState(2);  // 状态变化,通知所有观察者

    return 0;
}
UML 图
UML 图解析

Subject(主题) :也叫作观察目标,指被观察的对象。这里指 Subject 类。提供增加和删除观察者对象的接口,如 attachdetach

ConcreteSubject(具体主题) :维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 ConcreteSubject 类。

Observer(观察者) :定义了观察者的接口,所有具体观察者都需要实现这个接口。当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Observer 类。

ConcreteObserver(具体观察者) :实现了观察者接口,并注册到主题。当具体目标状态发生变化时,自身会接到通知。这里指 ConcreteObserver 类。

例二:工作流程

下面是逐步实现游戏聊天系统,使用观察者模式来管理玩家之间的聊天信息,每个玩家可以选择加入或退出家族聊天,系统会自动处理通知。将按照设计思路,逐步构建代码。

  1. 观察者注册到被观察者 :在主函数中,使用 addToList 方法将玩家注册到聊天通知器。
  2. 被观察者的状态发生变化 :调用 SayWords 方法改变聊天内容。
  3. 被观察者调用通知方法 :在 SayWords 方法中,调用 notify 方法通知所有注册的玩家。
  4. 观察者接收到通知后,更新自身状态 :每个玩家实现 NotifyWords 方法,接收到通知后输出聊天信息。
第一步:定义观察者接口

首先,定义一个观察者接口,所有玩家都需要实现这个接口,以接收聊天信息的更新通知。

cpp 复制代码
// 观察者接口
class Notifier {
public:
    virtual void addToList(Fighter* player) = 0; // 添加玩家
    virtual void removeFromList(Fighter* player) = 0; // 移除玩家
    virtual void notify(Fighter* talker, const std::string& message) = 0; // 通知玩家
    virtual ~Notifier() {}
};
第二步:定义主题接口

接下来,定义一个定义主题接口,玩家需要维护聊天通知器,并提供添加、删除玩家的方法。

cpp 复制代码
// 被观察者接口
class Fighter {
public:
    virtual void NotifyWords(Fighter* talker, const std::string& message) = 0; // 更新接口
    virtual void SetFamilyID(int id) = 0; // 设置家族ID
    virtual int GetFamilyID() const = 0; // 获取家族ID
    virtual ~Fighter() {}
};
第三步:实现具体主题(玩家类)

实现一个具体玩家类,维护玩家名称和家族ID,并在收到聊天信息时更新状态。

cpp 复制代码
#include <iostream>
#include <string>
#include <list>
#include <map>
#include <algorithm> // 用于 std::remove

// 具体主题:玩家
class ConcreteFighter : public Fighter {
private:
    std::string name; // 玩家名称
    int familyID; // 家族ID

public:
    ConcreteFighter(const std::string& playerName) : name(playerName), familyID(-1) {}

    void SetFamilyID(int id) override {
        familyID = id;
    }

    int GetFamilyID() const override {
        return familyID;
    }

    void NotifyWords(Fighter* talker, const std::string& message) override {
        std::cout << "玩家 " << name << " 收到了来自 " << talker->GetFamilyID() << " 的消息: " << message << std::endl;
    }
};
第四步:实现具体观察者(聊天通知器)

实现聊天通知器类,维护玩家列表,并在聊天内容变化时通知玩家。

cpp 复制代码
// 具体观察者:聊天通知器
class TalkNotifier : public Notifier {
private:
    std::map<int, std::list<Fighter*>> familyList; // 家族ID与玩家列表的映射

public:
    void addToList(Fighter* player) override {
        int familyID = player->GetFamilyID();
        if (familyID != -1) {
            familyList[familyID].push_back(player); // 添加玩家到对应家族
        }
    }

    void removeFromList(Fighter* player) override {
        int familyID = player->GetFamilyID();
        if (familyID != -1) {
            familyList[familyID].remove(player); // 移除玩家
        }
    }

    void notify(Fighter* talker, const std::string& message) override {
        int familyID = talker->GetFamilyID();
        if (familyID != -1) {
            for (Fighter* player : familyList[familyID]) {
                player->NotifyWords(talker, message); // 通知所有玩家
            }
        }
    }
};
第五步:主函数

在主函数中,创建玩家和聊天通知器对象,注册玩家,并演示聊天信息的通知过程。

cpp 复制代码
// 主函数
int main() {
    // 创建聊天通知器
    TalkNotifier notifier;

    // 创建玩家
    ConcreteFighter player1("张三");
    player1.SetFamilyID(100);
    ConcreteFighter player2("李四");
    player2.SetFamilyID(100);
    ConcreteFighter player3("王五");
    player3.SetFamilyID(200);

    // 注册玩家到通知器
    notifier.addToList(&player1);
    notifier.addToList(&player2);
    notifier.addToList(&player3);

    // 玩家发送消息
    player1.SayWords("大家集合,准备进攻!", &notifier); // 张三发送消息
    player2.SayWords("听从指挥,前往目标!", &notifier); // 李四发送消息

    // 移除玩家
    notifier.removeFromList(&player2); // 移除李四

    // 再次发送消息
    player1.SayWords("李四已经不在了,继续行动!", &notifier); // 张三发送消息

    return 0;
}
UML 图
UML 图解析

Subject(主题) :也叫作观察目标,指被观察的对象。这里指 Notifier 类。提供增加和删除观察者对象的接口,如 addToListremoveFromList

ConcreteSubject(具体主题) :维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 TalkNotifier 子类。

Observer(观察者) :当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Fighter 类。

ConcreteObserver(具体观察者) :调用观察目标的 addToList 成员函数将自身加入到观察者列表中,当具体目标状态发生变化时,自身会接到通知(NotifyWords 成员函数会被调用)。这里指 F_WarriorF_Mage 子类。

优缺点

优点

  • 松耦合:观察者和主题之间的关系是松散的,降低了模块间的耦合度。
  • 动态添加观察者:可以在运行时增加或减少观察者。
  • 多对一的通知:一个主题可以通知多个观察者,适合事件驱动的场景。

缺点

  • 通知开销:如果观察者数量较多,通知开销可能较大。
  • 循环依赖:若观察者和主题相互依赖,可能导致循环更新。

适用场景

  • 事件处理系统:GUI框架中,用户操作(如点击按钮)会触发事件,多个组件需要对这些事件做出反应。

  • 数据绑定:在MVC(模型-视图-控制器)架构中,当模型数据变化时,视图需要自动更新以反映最新的数据。

  • 发布-订阅系统:在消息传递系统中,多个订阅者会对特定主题的消息做出反应,发布者只需发布消息而不需要关心订阅者的具体实现。

  • 状态监控:在监控系统中,多个观察者需要监控同一状态(如温度传感器的读数),当状态变化时,所有观察者都能及时获取更新。

  • 社交媒体通知:当用户发布新内容时,所有关注该用户的粉丝会收到更新通知。

例一:完整代码

将以上步骤组合在一起,完整代码如下:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove

// 观察者接口
class Observer {
public:
    virtual void update(int state) = 0; // 更新接口
};

// 主题接口
class Subject {
public:
    virtual void attach(Observer* observer) = 0; // 添加观察者
    virtual void detach(Observer* observer) = 0; // 移除观察者
    virtual void notify() = 0; // 通知观察者
};

// 具体主题
class ConcreteSubject : public Subject {
private:
    std::vector<Observer*> observers; // 观察者列表
    int state; // 主题的状态

public:
    void attach(Observer* observer) override {
        observers.push_back(observer); // 添加观察者
    }

    void detach(Observer* observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者
    }

    void notify() override {
        for (Observer* observer : observers) {
            observer->update(state); // 通知所有观察者
        }
    }

    void setState(int newState) {
        state = newState; // 更新状态
        notify(); // 状态变化时通知观察者
    }

    int getState() const {
        return state; // 获取当前状态
    }
};

// 具体观察者
class ConcreteObserver : public Observer {
private:
    std::string name; // 观察者名称
    int observedState; // 观察者的状态

public:
    ConcreteObserver(const std::string& name) : name(name) {}

    void update(int state) override {
        observedState = state; // 更新观察者的状态
        std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;
    }
};

// 主函数
int main() {
    ConcreteSubject subject; // 创建具体主题

    ConcreteObserver observer1("Observer 1"); // 创建观察者1
    ConcreteObserver observer2("Observer 2"); // 创建观察者2

    subject.attach(&observer1); // 注册观察者1
    subject.attach(&observer2); // 注册观察者2

    subject.setState(1);  // 状态变化,通知所有观察者
    subject.setState(2);  // 状态变化,通知所有观察者

    return 0;
}

例二:完整代码

cpp 复制代码
#include <iostream>
#include <list>
#include <map>

using namespace std;

class Fighter; //类前向声明
class Notifier //通知器父类
{
public:
    virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加到列表中
    virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除
    virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息
    virtual ~Notifier() {}
};
class Fighter
{
public:
    Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName)//构造函数
    {
        m_iFamilyID = -1; //-1表示没加入任何家族
    }
    virtual ~Fighter() {} //析构函数

public:
    void SetFamilyID(int tmpID)  //加入家族时设置家族ID
    {
        m_iFamilyID = tmpID;
    }
    int GetFamilyID() //获取家族ID
    {
        return m_iFamilyID;
    }

    void SayWords(string tmpContent, Notifier* notifier) //玩家说了某句话
    {
        notifier->notify(this, tmpContent);
    }

    //通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同的动作
    virtual void NotifyWords(Fighter* talker, string tmpContent)
    {
        //显示信息
        cout << "玩家:" << m_sPlayerName << " 收到了玩家:" << talker->m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl;
    }
private:
    int m_iPlayerID; //玩家ID,全局唯一
    string m_sPlayerName;  //玩家名字

    int m_iFamilyID;   //家族ID
};
//"战士"类玩家,父类为Fighter
class F_Warrior :public Fighter
{
public:
    F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};

//"法师"类玩家,父类为Fighter
class F_Mage :public Fighter
{
public:
    F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};

//----------------------------------
class TalkNotifier :public Notifier  //聊天信息通知器
{
public:
    //将玩家增加到家族列表中来
    virtual void addToList(Fighter* player)
    {
        int tmpfamilyid = player->GetFamilyID();
        if (tmpfamilyid != -1) //加入了某个家族
        {
            auto iter = m_familyList.find(tmpfamilyid);
            if (iter != m_familyList.end())
            {
                //该家族id在map中已经存在
                iter->second.push_back(player); //直接把该玩家加入到该家族
            }
            else
            {
                //该家族id在map中不存在
                list<Fighter*> tmpplayerlist;
                m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist));//以该家族id为key,增加条目到map中
                m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家
            }
        }
    }
    //将玩家从家族列表中删除
    virtual void removeFromList(Fighter* player)
    {
        int tmpfamilyid = player->GetFamilyID();
        if (tmpfamilyid != -1) //加入了某个家族
        {
            auto iter = m_familyList.find(tmpfamilyid);
            if (iter != m_familyList.end())
            {
                m_familyList[tmpfamilyid].remove(player);
            }
        }
    }

    //家族中某玩家说了句话,调用该函数来通知家族中所有人
    virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家
    {
        int tmpfamilyid = talker->GetFamilyID();
        if (tmpfamilyid != -1)
        {
            auto itermap = m_familyList.find(tmpfamilyid);
            if (itermap != m_familyList.end())
            {
                //遍历该玩家所属家族的所有成员
                for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist)
                {
                    (*iterlist)->NotifyWords(talker, tmpContent);
                }
            }
        }
    }
private:
    //map中的key表示家族id,value代表该家族中所有玩家列表
    map<int, list<Fighter*> > m_familyList; //增加#include <map>
};


int main()
{
    //创建游戏玩家
    Fighter* pplayerobj1 = new F_Warrior(10, "张三");
    pplayerobj1->SetFamilyID(100);

    Fighter* pplayerobj2 = new F_Warrior(20, "李四");
    pplayerobj2->SetFamilyID(100);

    Fighter* pplayerobj3 = new F_Mage(30, "王五");
    pplayerobj3->SetFamilyID(100);

    Fighter* pplayerobj4 = new F_Mage(50, "赵六");
    pplayerobj4->SetFamilyID(200);

    //创建通知器
    Notifier* ptalknotify = new TalkNotifier();

    //玩家增加到家族列表中来,这样才能收到家族聊天信息
    ptalknotify->addToList(pplayerobj1);
    ptalknotify->addToList(pplayerobj2);
    ptalknotify->addToList(pplayerobj3);
    ptalknotify->addToList(pplayerobj4);

    //某游戏玩家聊天,同族人都应该收到该信息
    pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify);

    cout << "王五不想再收到家族其他成员的聊天信息了---" << endl;
    ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除

    pplayerobj2->SayWords("请大家听从族长的调遣,前往沼泽地!", ptalknotify);

    //释放资源
    delete pplayerobj1;
    delete pplayerobj2;
    delete pplayerobj3;
    delete pplayerobj4;
    delete ptalknotify;

    return 0;
}
相关推荐
南东山人3 小时前
一文说清:C和C++混合编程
c语言·c++
Ysjt | 深6 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__6 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word7 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆7 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz7 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE7 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy8 小时前
c++ 笔记
开发语言·c++
fengbizhe8 小时前
笔试-笔记2
c++·笔记