0
点赞
收藏
分享

微信扫一扫

游戏服务器架构-设计模式之观察者模式和发布订阅模式真的一样吗?

前面我给大家分享了观察者模式和发布订阅模式,有人私信给我说这俩不是一样嘛,大体没什么区别,我猜测大多数认为这两者是一样的可以继续阅读这两篇文章,如果还不能解答你的问题,我相信这篇文章对比两者的关系会让你有更深刻的认识。


观察者模式的松耦合

观察者模式,其实就是为了实现松耦合(loosely coupled)。

怎么使用观察者模式,面向接口编程,实现松耦合。

观察者模式里面,​​changed()​​方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了:


这里我再贴一块代码,BasePlayer是游戏玩家基类,Player和NPCPlayer是真实玩家类和怪物AI,WorldBossSystem是世界BOSS战斗系统,这里会有多个世界BOSS战的玩法,每个玩家可以参加这里的不同玩法,当世界boss活动开启后,就会通知当前参加的所有玩家来进入战场观看或者参加战斗:


# include<iostream>
using namespace std;
# include<unordered_map>
//Player基类
class BasePlayer
{
public:
BasePlayer(string name):_name(name) {}
//玩家参加世界BOSS战事件
virtual void JoinWorldBoss(int activity_id) = 0;
protected:
string _name;
};
//两个学生
class Player :public BasePlayer
{
public:
Player(string name):BasePlayer(name){}
void JoinWorldBoss(int activity_id)
{
cout << "玩家" << _name << "参加世界boss" << activity_id << endl;
}
};
class NPCPlayer :public BasePlayer
{
public:
NPCPlayer(string name) :BasePlayer(name) {}
void JoinWorldBoss(int activity_id)
{
cout << "AI" << _name << "参加世界BOSS" << activity_id << endl;
}
};
//世界BOSS系统战场
class WorldBossSystem
{
public:
//玩家选择阵营
void Select(BasePlayer* player, int activity_id)
{
//判断当前课程是第一次被人选还是已经有人选过了
unordered_map<int, list<BasePlayer*>>::iterator it = _union.find(activity_id);
if (it == _union.end())
{
_union[activity_id].push_back(player);
}
else
{
it->second.push_back(player);
}
}

//通知玩家来进入世界boss战斗场景参加战斗或观看
void NoticePlayer(int activity_id)
{
unordered_map<int, list<BasePlayer*>>::iterator it = _union.find(activity_id);
if (it != _union.end())
{
for (BasePlayer* player : it->second)
{
player->JoinWorldBoss(activity_id);
}
}
}

private:
//记录玩家和世界BOSS活动的信息
unordered_map<int, list<BasePlayer*>> _union;

};
int main()
{
//实例一个玩家和怪物AI
BasePlayer* plr_1 = new Player("游戏开发高司机");
BasePlayer* plr_2 = new Player("爱写BUG的高司机");
BasePlayer* npc_1 = new NPCPlayer("游戏怪物AI");
BasePlayer* npc_2 = new NPCPlayer("游戏怪物AI2");
//世界BOSS系统战场
WorldBossSystem world_boss;
//游戏开发高司机选择世界boss活动10000
world_boss.Select(plr_1, 10000);
//爱写BUG的高司机选择世界boss活动10000
world_boss.Select(plr_2, 10000);

//游戏怪物AI选择世界boss活动10001
world_boss.Select(npc_1, 10001);
//游戏怪物AI2选择世界boss活动10001
world_boss.Select(npc_2, 10001);
//TODO 做一些其他游戏逻辑
//定时器时间到了通知让参加世界boss战的玩家来战场地图参与战斗或观看
world_boss.NoticePlayer(10000);
world_boss.NoticePlayer(10001);
delete plr_1;
delete plr_2;
delete npc_1;
delete npc_2;
}

发布订阅的松耦合

大概很多人觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。

其实并不是。

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

互不相识?那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。


发布者只需告诉Broker,我要发的消息,topic是AAA;

订阅者只需告诉Broker,我要订阅topic是AAA的消息;

于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。



 

从图中可以看出,观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。 

总结

从表面上看:

  • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
  • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker

往更深层次讲:

  • 观察者和被观察者,是松耦合的关系
  • 发布者和订阅者,则完全不存在耦合

从使用层面上讲:

  • 这两种模式区别可以简单归结为是观察状态还是事件。
  • 观察者模式中
  • 状态发布者维护了一个观察者的列表,明确的知道有哪些观察者存在,将状态变化直接通知给观察者
  • 状态的观察者也明确的知道自己观察的状态是描述的哪一个对象
  • 甚至需要这种相互知道的关系来处理逻辑(比如需要明确知道哪一个按钮被点击,处理对应的逻辑)
  • 发布/订阅模式中
  • 事件的发布者只发布事件,不关心这个事件被谁获取了,通常将事件发给一个中间件,由中间件再去分发事件
  • 事件的订阅者只关心事件本身,不关心这个事件是谁发布的,通常在中间件中去注册观察某个事件
  • 中间件中去维护事件类别对应的订阅者列表,当收到事件后,去对应列表中通知订阅者们


举报

相关推荐

0 条评论