第五章 观察者(Observer)模式

第五章 观察者(Observer)模式

章节定位与核心目标

本章是行为型设计模式的核心内容之一,承接第 4 章策略模式的 “依赖倒置原则”,通过网络游戏家族聊天功能的代码迭代,解决 “一对多对象依赖” 场景下的高耦合低效率问题,最终掌握观察者模式的设计思想、结构、角色及实际应用。

第一节 一个遍历问题导致的低效率范例

1.1业务场景背景

A 公司将单机打斗游戏改造为多人网游,策划提出家族聊天功能需求

  • 玩家可加入家族(家族 ID 标识,-1 表示未加入),家族最多容纳 20 人;
  • 家族成员聊天信息仅本家族可见,成员可屏蔽信息;
  • 非家族成员无法接收该家族聊天内容。

1.2 第一版代码实现逻辑

1.2.1核心类结构与成员

namespace _nmsp1
{
	class Fighter; //类前向声明
	list<Fighter*> g_playerList; 

	//玩家父类(以往的战斗者类)
	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;
		}

	public:
		void SayWords(string tmpContent) //玩家说了某句话
		{
			if (m_iFamilyID != -1)
			{
				//该玩家属于某个家族,应该把聊天内容信息传送给该家族的其他玩家
				for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter)
				{
					if (m_iFamilyID == (*iter)->m_iFamilyID)
					{
						//同一个家族的其他玩家也应该收到聊天信息
						NotifyWords((*iter), tmpContent);
					}
				}
			}
		}
	private:
		void NotifyWords(Fighter* otherPlayer, string tmpContent) //其他玩家收到了当前玩家的聊天信息
		{
			//显示信息
			cout << "玩家:" << otherPlayer->m_sPlayerName << "收到了玩家:" << 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) {} //构造函数
	};
}

1.2.2核心功能:聊天信息的发送与接收

  • 发送逻辑(SayWords):

    1. 先判断玩家是否加入家族(m_iFamilyID != -1);

    2. 遍历全局玩家列表g_playerList

    3. 对比遍历对象与当前玩家的家族 ID,相同则调用NotifyWords推送信息。

      void Fighter::SayWords(string tmpContent) 
      {
          if (m_iFamilyID != -1)
          {
              for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter) 
              {
                  if (m_iFamilyID == (*iter)->m_iFamilyID)
                  {
                      NotifyWords((*iter), tmpContent);
                  }
              }
          }
      }
      
  • 接收逻辑(NotifyWords):打印接收者、发送者及聊天内容,完成信息展示。

完整代码:

// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注

#include <iostream>
#include <list>
#include <map>

#ifdef _DEBUG   //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif

//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996) 

namespace _nmsp1
{
	class Fighter; //类前向声明
	list<Fighter*> g_playerList; 

	//玩家父类(以往的战斗者类)
	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;
		}

	public:
		void SayWords(string tmpContent) //玩家说了某句话
		{
			if (m_iFamilyID != -1)
			{
				//该玩家属于某个家族,应该把聊天内容信息传送给该家族的其他玩家
				for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter)
				{
					if (m_iFamilyID == (*iter)->m_iFamilyID)
					{
						//同一个家族的其他玩家也应该收到聊天信息
						NotifyWords((*iter), tmpContent);
					}
				}
			}
		}
	private:
		void NotifyWords(Fighter* otherPlayer, string tmpContent) //其他玩家收到了当前玩家的聊天信息
		{
			//显示信息
			cout << "玩家:" << otherPlayer->m_sPlayerName << "收到了玩家:" << 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) {} //构造函数
	};

}

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口

	
	//创建游戏玩家
	_nmsp1::Fighter* pplayerobj1 = new _nmsp1::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
	pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
	_nmsp1::g_playerList.push_back(pplayerobj1); //加入到全局玩家列表中


	_nmsp1::Fighter* pplayerobj2 = new _nmsp1::F_Warrior(20, "李四");
	pplayerobj2->SetFamilyID(100);
	_nmsp1::g_playerList.push_back(pplayerobj2);

	_nmsp1::Fighter* pplayerobj3 = new _nmsp1::F_Mage(30, "王五");
	pplayerobj3->SetFamilyID(100);
	_nmsp1::g_playerList.push_back(pplayerobj3);

	_nmsp1::Fighter* pplayerobj4 = new _nmsp1::F_Mage(50, "赵六");
	pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
	_nmsp1::g_playerList.push_back(pplayerobj4);

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

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

	return 0;
}

1.2.3测试案例与效果

// 创建4个玩家:张三/李四/王五(家族100)、赵六(家族200)
_nmsp1::Fighter* pplayerobj1 = new _nmsp1::F_Warrior(10, "张三");
pplayerobj1->SetFamilyID(100);
_nmsp1::g_playerList.push_back(pplayerobj1);
// 李四、王五、赵六创建逻辑类似,此处省略
pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!");
  • 效果:张三、李四、王五能收到信息,赵六无法接收,功能符合需求。

1.3第一版代码的核心缺陷

  1. 效率极低:若网游有上万个玩家,每次聊天需遍历全服玩家,而非仅本家族(最多 20 人);
  2. 耦合度高Fighter类直接依赖全局变量g_playerList,若列表存储结构变更(如改用 map),需修改Fighter源码;
  3. 扩展性差:实现 “屏蔽家族信息” 需修改SayWords的遍历逻辑,违反开闭原则
  4. 职责不单一Fighter既负责玩家属性管理,又负责聊天信息的遍历推送,违背单一职责原则

第二节 引入观察者(Observer)模式

1.2.1优化核心思路

  • 分组存储:按家族 ID 分组管理玩家,避免全服遍历;
  • 抽象解耦:分离 “通知逻辑” 与 “玩家逻辑”,定义Notifier抽象类解耦依赖;
  • 动态管理:支持观察者(玩家)的动态添加 / 移除,实现 “屏蔽信息” 功能。

1.2.2 代码迭代对比(从_nmsp1 到_nmsp2 的关键变更)

迭代点 _nmsp1 实现 _nmsp2 实现
通知逻辑载体 嵌入 Fighter 类,依赖全局列表 抽象为 Notifier 类,子类实现具体逻辑
玩家家族 ID 获取 无公开接口 新增GetFamilyID公有方法
聊天发送接口 SayWords(string),自行遍历 SayWords(string, Notifier*),调用通知器
信息接收接口 私有方法NotifyWords,不可扩展 公有虚方法NotifyWords,支持子类重写
玩家存储结构 全局 list,无分组 map<int, list<Fighter*>>,按家族分组

(1)抽象通知器(Subject / 观察目标):Notifier 类

定义观察者的添加、移除、通知接口,实现 “依赖倒置”,让观察目标依赖抽象而非具体观察者:

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() {}
};

(2)观察者(Observer):Fighter 类的改造

class Fighter//玩家父类
{
public:
    Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
    {
        m_iFamilyID = -1; //-1表示没有加入任何家族
    }
    virtual ~Fighter() {} //析构函数

public:
    //加入家族的时候要设置家族ID
    void SetFamilyID(int tmpID) {  m_iFamilyID = tmpID;  }
    // 新增:获取家族ID(给通知器提供分组依据)
    int GetFamilyID() { return m_iFamilyID; }
    
public:
    // 改造:发送聊天需传入通知器,自身不再处理遍历逻辑
    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
};

(3)具体通知器(ConcreteSubject):TalkNotifier 类

实现Notifier接口,用map按家族 ID 分组存储玩家,完成高效通知:

//聊天信息通知器
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;
};

(4)优化后测试案例

// 创建玩家(逻辑同_nmsp1,省略)
// 创建具体通知器
_nmsp2::Notifier* ptalknotify = new _nmsp2::TalkNotifier();
// 玩家加入家族列表(注册为观察者)
ptalknotify->addToList(pplayerobj1);
ptalknotify->addToList(pplayerobj2);
ptalknotify->addToList(pplayerobj3);
ptalknotify->addToList(pplayerobj4);

// 发送聊天(调用通知器广播)
pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify);

// 王五屏蔽家族信息(移除观察者)
ptalknotify->removeFromList(pplayerobj3);
pplayerobj2->SayWords("请大家听从族长调遣,前往沼泽地!", ptalknotify);
  • 效果:第一次聊天张三 / 李四 / 王五均可接收,第二次王五因被移除无法接收,功能满足且效率大幅提升。

完整代码:

// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注

#include <iostream>
#include <list>
#include <map>

#ifdef _DEBUG   //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif

//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996) 

namespace _nmsp2
{
	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;
		}

	public:
		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;
	};
}

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口

	//第5章 观察者(Observer)模式
	//(1)一个遍历问题导致的低效率范例
	//(2)引入观察者(Observer)模式
	 //观察者设计模式 定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于
	    //它的对象都会自动得到通知。
	//发布-订阅(Publish-Subscribe);

	//观察者模式的四种角色
	//a)Subject(主题):观察目标,这里指Notifier类。
	//b)ConcreteSubject(具体主题):这里指TalkNotifier类。
	//c)Observer(观察者):这里指Fighter类。
	//d)ConcreteObserver(具体观察者):这里指F_Warrior和F_Mage子类。

	//观察者模式的特点:
	//a)在观察者和观察目标之间建立了一个抽象的耦合
	//b)观察目标会向观察者列表中的所有观察者发送通知。
	//c)可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则
	//(3)应用联想
	//a)救援家族成员镖车
	//b)将新闻推荐给符合其胃口的读者
	//c)通过改变自身绘制的图形来真实的反应公司的销售数据。
	//d)炮楼只会对30米内的玩家(列表内玩家)进行攻击。

	//创建游戏玩家
	_nmsp2::Fighter* pplayerobj1 = new _nmsp2::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
	pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
	
	_nmsp2::Fighter* pplayerobj2 = new _nmsp2::F_Warrior(20, "李四");
	pplayerobj2->SetFamilyID(100);
	
	_nmsp2::Fighter* pplayerobj3 = new _nmsp2::F_Mage(30, "王五");
	pplayerobj3->SetFamilyID(100);
	
	_nmsp2::Fighter* pplayerobj4 = new _nmsp2::F_Mage(50, "赵六");
	pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
	
	//创建通知器
	_nmsp2::Notifier* ptalknotify = new _nmsp2::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;
}

1.2.3 观察者模式的核心概念(结合 PPT UML 图)

(1)模式定义

也叫发布 - 订阅(Publish-Subscribe)模式,定义对象间的一对多依赖关系,当观察目标状态改变时,所有依赖的观察者会自动收到通知。

(2)四种核心角色(对应代码)

角色 对应代码类 核心职责
Subject(主题 / 观察目标) Notifier 抽象类 定义观察者的添加、移除、通知接口(addToList/removeFromList/notify
ConcreteSubject(具体主题) TalkNotifier 类 维护观察者列表(家族 - 玩家 map),状态变更时调用notify广播通知
Observer(观察者) Fighter 类 定义接收通知的接口(NotifyWords虚函数),被动接收状态变更通知
ConcreteObserver(具体观察者) F_Warrior/F_Mage 继承 Observer,可重写NotifyWords实现个性化信息接收逻辑

(3)UML 图说明

  • 图 5.1(标准观察者 UML):包含Notifier抽象层,Fighter为观察者基类,TalkNotifierF_Warrior/F_Mage为具体实现,虚线表示Notifier依赖Fighter
  • 图 5.2(未抽象通知器):直接用TalkNotifier作为观察目标,省略Notifier抽象层,适用于通知逻辑单一的场景;
  • 图 5.3(角色关系图):具体主题(通知器)状态变更时,主动通知所有具体观察者(家族玩家),实现一对多联动。

(4)模式特点

  1. 抽象耦合,松绑定:观察目标依赖观察者抽象,而非具体类,满足依赖倒置原则,一方变更不影响另一方;
  2. 主动通知,简化设计:观察目标主动推送状态变更,无需观察者轮询,降低系统设计复杂度;
  3. 开闭兼容,易扩展:新增观察者(如弓箭手职业)或观察目标(如系统公告通知器),只需新增子类,无需修改原有代码。

第三节 应用联想

结合模式核心思想,可拓展到以下业务场景:

  1. 家族成员镖车救援(征途游戏案例)
    • 观察目标:被攻击的镖车;观察者:本家族成员;
    • 镖车被攻击时(状态变更),自动通知家族成员,成员可点击通知快速驰援。
  2. 门户网站新闻推荐
    • 观察目标:不同类型新闻(国际 / 娱乐 / 美食);观察者:订阅对应分类的用户;
    • 新新闻发布时(状态变更),自动推送给订阅该分类的用户,实现精准触达。
  3. 销售数据多图表展示
    • 观察目标:公司销售数据;观察者:饼图 / 柱状图 / 折线图组件;
    • 数据更新时(状态变更),同步通知所有图表组件,自动刷新图形展示最新数据。
  4. 网游炮楼自动攻击
    • 观察目标:炮楼;观察者:30 米范围内的玩家;
    • 玩家进入 / 离开 30 米范围(状态变更),炮楼更新观察者列表,仅对列表内玩家发起攻击。

代码迭代总结

迭代阶段 存储结构 遍历范围 耦合程度 扩展性 效率
第一版(_nmsp1) 全局 list 全服玩家 高耦合 极低
第二版(_nmsp2) 家族分组 map 仅当前家族玩家 低耦合 极高

观察者模式通过抽象解耦分组管理,既解决了遍历效率问题,又满足了开闭原则,是一对多依赖场景的最优解之一。

参开资料来源:《C++新经典-设计模式》

posted @ 2025-12-13 12:32  CodeMagicianT  阅读(3)  评论(0)    收藏  举报