1. 一个遍历问题导致的低效率范例

1.1 场景:

在线游戏中,多人聊天。同家族的所有成员可收到家族成员的聊天内容,非本家族不能收到。

1.2 示例代码

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <list>
  5. using namespace std;
  6. class Fighter;
  7. list<Fighter*> g_playerList;
  8. // 玩家父类(以往的战斗者类)
  9. class Fighter {
  10. public:
  11. Fighter(int playerID, string playerName)
  12. : m_playerID(playerID)
  13. , m_playerName(playerName)
  14. {
  15. // -1 表示没有加入任何家族
  16. m_familyID = -1;
  17. }
  18. virtual ~Fighter() { }
  19. // 加入家族的时候要设置家族ID
  20. void setFamilyID(int familyID) {
  21. m_familyID = familyID;
  22. }
  23. // 玩家说了某句话
  24. void sayWords(string content) {
  25. // 该玩家属于某个家族,应该把聊天内容信息传送到给该家族的其他玩家
  26. for (auto iter = g_playerList.begin(); iter != g_playerList.end(); iter++) {
  27. if (m_familyID == (*iter)->m_familyID){
  28. // 同一个家族的其他玩家也应该收到聊天信息
  29. notifyWords(*iter, content);
  30. }
  31. }
  32. }
  33. private:
  34. void notifyWords(Fighter* otherPlayer, string content) {
  35. // 显示信息
  36. cout << otherPlayer->m_playerName << " receives the message sent by " << m_playerName << ", the chat message sent was" << content << endl;
  37. }
  38. private:
  39. // 玩家ID,全局唯一
  40. int m_playerID;
  41. // 玩家名字
  42. string m_playerName;
  43. // 家族ID
  44. int m_familyID;
  45. };
  46. // 战士类玩家,父类为Fighter
  47. class Warrior :public Fighter {
  48. public:
  49. Warrior(int playerID, string playerName)
  50. : Fighter(playerID, playerName)
  51. {
  52. }
  53. };
  54. // 法师类玩家,父类为Fighter
  55. class Mage :public Fighter {
  56. public:
  57. Mage(int playerID, string playerName)
  58. : Fighter(playerID, playerName)
  59. {
  60. }
  61. };
  62. void observer1() {
  63. // 创建游戏玩家(实际数据应来自数据库)
  64. Fighter* warrior1 = new Warrior(10, "fighter1");
  65. warrior1->setFamilyID(100);
  66. g_playerList.push_back(warrior1);
  67. Fighter* warrior2 = new Warrior(20, "fighter2");
  68. warrior2->setFamilyID(100);
  69. g_playerList.push_back(warrior2);
  70. Fighter* mage1 = new Mage(30, "mage1");
  71. mage1->setFamilyID(100);
  72. g_playerList.push_back(mage1);
  73. Fighter* mage2 = new Mage(40, "mage2");
  74. mage2->setFamilyID(200);
  75. g_playerList.push_back(mage2);
  76. // 当某个玩家聊天时,同族人都应该收到该信息
  77. warrior1->sayWords("The whole tribe immediately assembled in the marshes.");
  78. delete warrior1;
  79. delete warrior2;
  80. delete mage1;
  81. delete mage2;
  82. }

上述代码实现了该功能需求,但效率不高。sayWords这个函数中的for循环,如果玩家数量比较少的时候不受影响,但玩家数量增多至数万时就需要考虑效率问题,此时引入观察者模式。

2. 引入观察者模式

2.1 定义(实现意图)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。观察者模式又称观察订阅模式。

2.2 示例代码

  1. #pragma once
  2. #include <iostream>
  3. #include <list>
  4. #include <map>
  5. #include <string>
  6. using namespace std;
  7. class Fighter; // 类前向声明
  8. // 通知器父类
  9. class Notifier {
  10. public:
  11. // 把要被通知的玩家加入到列表中
  12. virtual void addToList(Fighter* player) = 0;
  13. // 把不想被通知的玩家从列表中去除
  14. virtual void removeFromList(Fighter* player) = 0;
  15. // 通知的一些细节信息
  16. virtual void notify(Fighter* talker, string comtent) = 0;
  17. virtual ~Notifier() { }
  18. };
  19. // 玩家父类(以往的战斗者类)
  20. class Fighter {
  21. public:
  22. Fighter(int playerID, string playerName)
  23. : m_playerID(playerID)
  24. , m_playerName(playerName)
  25. {
  26. // -1 表示没有加入任何家族
  27. m_familyID = -1;
  28. }
  29. virtual ~Fighter() { }
  30. // 加入家族的时候要设置家族ID
  31. void setFamilyID(int familyID)
  32. {
  33. m_familyID = familyID;
  34. }
  35. // 获取家族ID
  36. int getFamilyID()
  37. {
  38. return m_familyID;
  39. }
  40. // 玩家说了某句话
  41. void sayWords(Notifier* notifier, string content)
  42. {
  43. notifier->notify(this, content);
  44. }
  45. // 通知该玩家接收到其他玩家发送来的聊天信息,虚函数子类可以覆盖以实现不同的功能
  46. void notifyWords(Fighter* talker, string content)
  47. {
  48. // 显示信息
  49. cout << m_playerName << " receives the message sent by " << talker->m_playerName
  50. << ", the chat message sent was" << content << endl;
  51. }
  52. private:
  53. // 玩家ID,全局唯一
  54. int m_playerID;
  55. // 玩家名字
  56. string m_playerName;
  57. // 家族ID
  58. int m_familyID;
  59. };
  60. // 战士类玩家,父类为Fighter
  61. class Warrior : public Fighter {
  62. public:
  63. Warrior(int playerID, string playerName)
  64. : Fighter(playerID, playerName)
  65. {
  66. }
  67. };
  68. // 法师类玩家,父类为Fighter
  69. class Mage : public Fighter {
  70. public:
  71. Mage(int playerID, string playerName)
  72. : Fighter(playerID, playerName)
  73. {
  74. }
  75. };
  76. // 聊天信息通知器
  77. class TalkNotifier : public Notifier {
  78. public:
  79. // 将玩家增加到家族列表中来
  80. void addToList(Fighter* player) override
  81. {
  82. int tempFamilyid = player->getFamilyID();
  83. if (tempFamilyid == -1) {
  84. return;
  85. }
  86. // 加入了某个家族
  87. auto iter = m_familyList.find(tempFamilyid);
  88. if (iter != m_familyList.end()) { // 家族ID存在
  89. iter->second.push_back(player);
  90. } else { // 家族ID不存在
  91. list<Fighter*> coll;
  92. m_familyList.insert(make_pair(tempFamilyid, coll));
  93. m_familyList[tempFamilyid].push_back(player);
  94. }
  95. }
  96. // 将玩家从家族列表中删除
  97. void removeFromList(Fighter* player) override
  98. {
  99. int tempFamilyid = player->getFamilyID();
  100. if (tempFamilyid == -1) {
  101. return;
  102. }
  103. auto iter = m_familyList.find(tempFamilyid);
  104. if (iter == m_familyList.end()) {
  105. return;
  106. }
  107. iter->second.remove(player);
  108. }
  109. // 家族中某玩家说了句话,调用该函数来通知家族中所有人
  110. void notify(Fighter* talker, string content) override
  111. {
  112. int tempFamilyid = talker->getFamilyID();
  113. if (tempFamilyid == -1) {
  114. return;
  115. }
  116. auto iter = m_familyList.find(tempFamilyid);
  117. if (iter == m_familyList.end()) {
  118. return;
  119. }
  120. list<Fighter*> coll = iter->second;
  121. // 遍历该家族的所有成员
  122. for (auto iterList = coll.begin(); iterList != coll.end(); ++iterList) {
  123. (*iterList)->notifyWords(talker, content);
  124. }
  125. }
  126. private:
  127. // key为家族ID, value代表该家族中所有玩家列表
  128. map<int, list<Fighter*>> m_familyList;
  129. };
  130. void observer1()
  131. {
  132. Fighter* warrior1 = new Warrior(10, "warrior1");
  133. warrior1->setFamilyID(100);
  134. Fighter* warrior2 = new Warrior(20, "warrior2");
  135. warrior2->setFamilyID(100);
  136. Fighter* mage1 = new Mage(30, "mage1");
  137. mage1->setFamilyID(100);
  138. Fighter* mage2 = new Mage(40, "mage2");
  139. mage2->setFamilyID(200);
  140. // 创建通知器
  141. Notifier* notifier = new TalkNotifier();
  142. // 玩家增加到家族列表中来,这样才能收到家族聊天记录
  143. notifier->addToList(warrior1);
  144. notifier->addToList(warrior2);
  145. notifier->addToList(mage1);
  146. notifier->addToList(mage2);
  147. // 当某游戏玩家聊天时,同族玩家都应该收到该信息
  148. string chatMsg1 = "The whole tribe immediately assembled in the marshes.";
  149. warrior1->sayWords(notifier, chatMsg1);
  150. cout << "Warrior2 doesn't want to receive any more chat messages from the rest of the family..." << endl;
  151. notifier->removeFromList(warrior2);
  152. string chatMsg2 = "Please follow the group leader's instructions.";
  153. mage1->sayWords(notifier, chatMsg2);
  154. delete warrior1;
  155. delete warrior2;
  156. delete mage1;
  157. delete mage2;
  158. delete notifier;
  159. }

2.3 类图

13.观察者模式.svg
观察者模式的四种角色

  • Subject(主题):观察目标,这里指Notifier类。
  • ConcreteSubject(具体主题):这里指TalkNotifier类。
  • Observer(观察者):这里指Fighter类。
  • ConcreteObserver(具体观察者):这里指Warrior和Mage子类。

2.4 观察者模式的特点

  • 在观察者和观察目标之间建立了一个抽象的耦合
  • 观察目标会向观察者列表中的所有观察者发送通知
  • 可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则

    2.5 应用联想

  • 救援家族成员镖车

  • 将新闻推荐给符合其胃口的读者
  • 通过改变自身绘制的图形来真实的反应公司的销售数据。
  • 炮楼只会对30米内的玩家(列表内玩家)进行攻击。