以一个实际的User登录某APP为例,概述在后台框架中,其可能使用到的设计模式
User进行登录验证成功后,进入OnLoginSuccess事件,将各个登录逻辑用单例模式包装成事件依次处理,
将内存中的m_user对象作为传参供单例mgr使用,其中例如DataMgr不需要修改user内容的应严格使用const
void User::OnLoginSuccess() { //更新上次登陆时间 TimerMgr::Instance().UpdateLastLoginTime(m_user); //返回给客户端User的信息用于显示 DataMgr::Instance().SendClientUserInfo(m_user); //将广告推送给User AdvertiseMgr::Instance().SendNewsToUser(m_user); }
单例模式
static创建的对象是线程安全的
class DataMgr { public: static DataMgr& Instance() { static DataMgr s_instance; return s_instance; } void SendClientUserInfo(const User& cUser); private: DataMgr(){}; ~DataMgr(){}; };
总结 : 单例模式满足进程中只存在一个实例化对象,为一些频繁调用的程序节省了系统资源
现在,你想为玩家提供一些促销活动,
有以下几个活动:
1.限时充值话费满100反1
2.登录赠送海底捞代金券100减2
3.一个蚂蚁森林的复刻版(产生能量、偷能量、收取能量)
分析可以发现,他们都有一些共同的特性:
1.都有开始时间和结束时间
2.都会在活动过程中获得XX
3.可能在活动过程中消耗xx
4.在获得了XX东西后会用一种方式告诉你(直接弹窗或者发一个红点)
总而言之,他们都是为了骗你进入APP,然后送你点甜头,让你花更多的钱或者提高活跃度!
那么事情就简单了,我们需要有这些逻辑
1.过期判断(跨过某个时间点过期) :
登录时可能会触发活动的过期
在线状态下不会触发登录,但是活动也可能会过期
2.User的物品变化(余额、能量、代金券等等)
所有User物品发生变化时可以增加一个监听事件,在监听事件处理这些变化
我们使用单例模式创建一个事件监听器,在各种事件发生时调用他
struct ItemData; class EventTrigger { public: static EventTrigger& Instance() { static EventTrigger s_instance; return s_instance; } //登录事件 OnLoginSuccess(User& user); //跨天事件 OnPassDay(User& user); //道具增加事件 OnAddItem(User& user, ItemData& cItemData); //道具减少事件 OnDelItem(User& user, ItemData& cItemData); };
比如可以这样处理
void User::OnLoginSuccess() { //更新上次登陆时间 TimerMgr::Instance().UpdateLastLoginTime(m_user); //返回给客户端User的信息用于显示 DataMgr::Instance().SendClientUserInfo(m_user); //将广告推送给User AdvertiseMgr::Instance().SendNewsToUser(m_user); //触发登录事件 EventTrigger::Instance().OnLoginSuccess(m_user); }
现在我们可以开始设计我们的活动类了,一个基础的活动类,
1.需要检查活动是否在有效期内
2.活动开始时会做一些处理(发优惠券)
3.活动结束时会做一些处理(删除过期的道具)
4.道具发生变化时需要做一些处理
5.活动过程中的各种操作都是独有的,不可复用
/*ActivityBase.h*/ struct ActivityData; //通知方法 enum MSG_TYPE { MSG_TYPE_ALL = 0,//丧心病狂的全部打包通知 MSG_TYPE_REDPOINT = 1,//红点提示 MSG_TYPE_WINDOWS = 2,//弹窗消息 MSG_TYPE_SHORTMSG = 3;//短信通知 }; //活动状态 enum ACT_STATUS { ACT_STATUS_DEFAULT = 0,//默认 ACT_STATUS_NOTOPEN = 1,//未开启 ACT_STATUS_OPEN = 2,//刚开启 ACT_STATUS_RUNNING = 3,//进行中 ACT_STATUS_CLOSE = 4,//刚关闭 ACT_STATUS_EXPIRED = 5;//已过期 }; class ActivityBase { public: //登录成功后的处理 virtual void OnLoinSuccess(); //跨天的处理 virtual void OnPassDay(); //获得道具的处理 virtual void OnAddItem(User& user); //失去道具的处理 virtual void OnDelItem(User& user); //是否可以发送消息给User virtual bool CanSendMsgToUser(User& user){return true}; //以某种形式发给玩家消息 virtual void SendMgsToUser(User& user, const MSG_TYPE &eMsgType); void SetActivityData(const ActivityData& cActivityData) { m_ActivityData = cActivityData; }; ActivityData& GetActivityData() { return m_ActivityData; }; private: //刷新活动 virtual void RefreshActcity(); //改变活动的状态 virtual void ChangeActcityStatus(ACT_STATUS& eStatus); //活动开始时的事件 virtual void OnActStart(User& user) = 0; //活动结束时的事件 virtual void OnActEnd(User& user) = 0; ActivityData m_ActivityData; };
OnActStart和OnActEnd都是纯虚函数,这是因为每个活动开始和结束的操作都不一样,我们不能设计通用的函数去处理它
那么OnLoinSuccess、OnPassDay他们会带来什么影响呢,显然他们最大的影响就是改变了活动的状态,可能会让活动开启或关闭
/*ActivityBase.cpp*/ void ActivityBase::OnLoinSuccess() { UpdateActcityStatus(); switch(m_ActivityData.Status) { case ACT_STATUS_NOTOPEN:break; case ACT_STATUS_OPEN: OnActStart(); ChangeActcityStatus(ACT_STATUS_RUNNING); break; case ACT_STATUS_RUNNING:break; case ACT_STATUS_CLOSE: OnActStop(); ChangeActcityStatus(ACT_STATUS_EXPIRED); break; case ACT_STATUS_EXPIRED:break; } } void ActivityBase::OnPassDay() { //因为逻辑和OnLoginSuccess一样,所以不用再写一遍 OnLoginSuccess(); }
同理,大部分物品的变化都是会给玩家通知,告诉玩家发生了什么(恭喜你获得了满100减1代金券!呵呵)
/*ActivityBase.cpp*/ void ActivityBase::OnAddItem(User& user) { if (CanSendMsgToUser(user)) { SendMsgToUser(user, m_ActivityData.MsgType); } } void ActivityBase::OnDelItem(User& user) { if (CanSendMsgToUser(user)) { SendMsgToUser(user, m_ActivityData.MsgType); } }
重头戏来了,终于进入到了关键
装饰器模式
先看看限时充值话费活动
/*ActivityTimeLimitTopUp.h*/ template <typename T> class SimpleSingleton; class ActivityTimeLimitTopUp : public SimpleSingleton<ActivityTimeLimitTopUp>, public ActivityBase { public: //充值话费消耗余额 virtual void OnDelItem(User& user); private: //活动开始时的事件 virtual void OnActStart(User& user); //活动结束时的事件 virtual void OnActEnd(User& user); }; /*ActivityTimeLimitTopUp.cpp*/ void ActivityTimeLimitTopUp::OnActStart(User& user) { //二话不说先告诉大家可以享受充值优惠啦 SendMsgToUser(user, m_ActivityData.MsgType); } void ActivityTimeLimitTopUp::OnActEnd(User& user) { //悄悄的结束,啥都不做 } void ActivityTimeLimitTopUp::OnDelItem(User& user) { //还你们1块钱 ReturnDiscountToUser(user); }
可以看到onloginsuccess/onpassday这些终于不用再ctrl c+v啦,他们会自动继承父类的方法
delitem因为需要所以重写了逻辑,但是总体而言只需要写一些自己独有的函数,整个类都变得清爽了
同理再看看其他两个活动
/*ActivitySeaBottomCatch.h*/ template <typename T> class SimpleSingleton; class ActivitySeaBottomCatch: public SimpleSingleton<ActivitySeaBottomCatch>, public ActivityBase { private: //活动开始时的事件 virtual void OnActStart(User& user); //活动结束时的事件 virtual void OnActEnd(User& user); //聪明的产品经理选择返利给客户 void ConvertCouponToMoney(User& user); }; /*ActivitySeaBottomCatch.cpp*/ void ActivitySeaBottomCatch::OnActStart(User& user) { //兑换券发给user GiveCouponToUser(user); //快来消费 SendMsgToUser(user, m_ActivityData.MsgType); } void ActivitySeaBottomCatch::OnActEnd(User& user) { //大出血,把活动结束没用的兑换券按一张1分钱还给user ConvertCouponToMoney(user); } /*ActivityTree.h*/ template <typename T> class SimpleSingleton; class ActivityTree: public SimpleSingleton<ActivityTree>, public ActivityBase { public: //消耗了某种东西(购物获得能量) virtual void OnDelItem(User& user); //增加了某种东西(增加步行数获得能量) virtual void OnAddItem(User& user); //偷别人的能量 void StealOtherEnergy(); //收自己的能量 void GetMyOwnEnergy(); //用能量浇水 void UseEnergyToWater(); //重写CanSend方法 virtual bool CanSendMsgToUser(User& user); private: //活动开始时的事件 virtual void OnActStart(User& user); //活动结束时的事件 virtual void OnActEnd(User& user); void AddEnergy(User& user); void DelEnergy(User& user); //如有有能量可以收,通知玩家去收能量 bool IfHaveEnergyCanGet(User& user); }; /*ActivityTree.cpp*/ void ActivityTree::OnActStart(User& user) { //告诉大家可以偷能量了 SendMsgToUser(user, m_ActivityData.MsgType); } void ActivityTree::OnActEnd(User& user) { //活动结束了,感谢参与!有缘再见! SendMsgToUser(user, m_ActivityData.MsgType); } void ActivityTree::OnAddItem(User& user) { //增加能量 AddEnergy(user); } void ActivityTree::OnDelItem(User& user) { //增加能量 AddEnergy(user); } void ActivityTree::StealOtherEnergy(User& user) { //增加能量 AddEnergy(user); } void ActivityTree::GetMyOwnEnergy(User& user) { //增加能量 AddEnergy(user); } void ActivityTree::UseEnergyToWater(User& user) { //减少能量 DelEnergy(user); } bool ActivityTree::CanSendMsgToUser(User& user) { if (IfHaveEnergyCanGet(user) { return true; } else return ActivityBase::CanSendMgeToUser(user); }
海底捞活动只需要在活动开始时发一张兑换券,而蚂蚁森林活动就复杂得多,重写了父类的CanSendMsg方法,其他新增的功能函数可以通过APP的TCP通信协议来调用。
三种不同的活动都是基于ActivityBase稍作修改继承来的子类。
总结 : 装饰器模式其实是将多个功能类的通用功能抽象出来,用一个父类做统一处理,
每一个子类自己可以在继承父类的基础上添加新的功能,大大减少了代码的行数
这一过程看起来很好,其实忽略了一个问题:
如何触发这些事件?
EventTrigger可以在各个事件发生时监听,但是活动的事件又不会自动被监听,还是要手动写上去
所以EventTrigger应该是这样的
/*EventTrigger.cpp*/ //登录事件 void EventTrigger::OnLoginSuccess(User& user) { ActivityTimeLimitTopUp::Instance().OnLoginSuccess(user); ActivitySeaBottomCatch::Instance().OnLoginSuccess(user); ActivityTree::Instance().OnLoginSuccess(user); } //跨天事件 void EventTrigger::OnPassDay(User& user) { ActivityTimeLimitTopUp::Instance().OnPassDay(user); ActivitySeaBottomCatch::Instance().OnPassDay(user); ActivityTree::Instance().OnPassDay(user); } //道具增加事件 void EventTrigger::OnAddItem(User& user, ItemData& cItemData) { ActivityTimeLimitTopUp::Instance().OnAddItem(user); ActivitySeaBottomCatch::Instance().OnAddItem(user); ActivityTree::Instance().OnAddItem(user); } //道具减少事件 void EventTrigger::OnDelItem(User& user, ItemData& cItemData) { ActivityTimeLimitTopUp::Instance().OnDelItem(user); ActivitySeaBottomCatch::Instance().OnDelItem(user); ActivityTree::Instance().OnDelItem(user); }
这样看起来挺蠢的,每多一个活动就要在所有的事件里面添加触发代码,每多一个事件就要为所有的活动写实现的代码
真让人头大,但是ActivityBase是没有实体的,整个程序运行过程中我们并没有创建他的实例对象,
其实我们需要的是有一个家伙,把所有活动都记录在内,能在发生事件的时候通知所有的活动去执行对应的事件,这样就省事多了,它就是
观察者模式
首先观察者初始化时要把所有的活动都添加到观察者列表中
enum ACTIVITY_TYPE { ACTIVITY_TYPE_TIMELIMITTOPUP = 1, ACTIVITY_TYPE_SEABOTTOMCATCH = 2, ACTIVITY_TYPE_TREE = 3; }; /*ActivityObserver.h*/ class ActivityObserver { public: static ActivityObserver& Instance() { static ActivityObserver s_instance; return s_instance; } //观察者需要初始化自己 void RegisterActivity(); private: //用map不用vector是考虑对特定活动做处理,索引速度更快 map<uint64_t, ActivityBase*> m_ActivityMap; } /*ActivityObserver.cpp*/ void ActivityObserver::RegisterActivity() { m_ActivityMap.clear(); m_ActivityMap.insert(std::make_pair(ACTIVITY_TYPE_TIMELIMITTOPUP, ActivityTimeLimitTopUp::Instance())); m_ActivityMap.insert(std::make_pair(ACTIVITY_TYPE_SEABOTTOMCATCH, ActivitySeaBottomCatch::Instance())); m_ActivityMap.insert(std::make_pair(ACTIVITY_TYPE_TREE, ActivityTree::Instance())); }
那么观察者什么时候初始化自己呢,我觉得在把它放在进程的启动函数里面是一个不错的选择,
毕竟程序启动以后,观察者就不需要动态的添加活动到自己的监视列表了
void System::Start() { ActivityObserver::Instance().RegisterActivity(); }
接下来我们依然需要把各个事件的触发逻辑完善
/*ActivityObserver.cpp*/ void ActivityObserver::OnLoginSuccess(User& user) { for (auto &it : m_ActivityMap) { it.sencond->OnLoginSuccess(user) } } void ActivityObserver::OnPassDay(User& user) { for (auto &it : m_ActivityMap) { it.sencond->OnPassDay(user) } } void ActivityObserver::OnAddItem(User& user) { for (auto &it : m_ActivityMap) { it.sencond->OnAddItem(user) } } void ActivityObserver::OnDelItem(User& user) { for (auto &it : m_ActivityMap) { it.sencond->OnDelItem(user) } }
最后再把观察者的事件填到事件触发器中去
/*EventTrigger.cpp*/ //登录事件 void EventTrigger::OnLoginSuccess(User& user) { ActivityObserver::Instance().OnLoginSuccess(user); } //跨天事件 void EventTrigger::OnPassDay(User& user) { ActivityObserver::Instance().OnPassDay(user); } //道具增加事件 void EventTrigger::OnAddItem(User& user, ItemData& cItemData) { ActivityObserver::Instance().OnAddItem(user); } //道具减少事件 void EventTrigger::OnDelItem(User& user, ItemData& cItemData) { ActivityObserver::Instance().OnDelItem(user); }
这样再加入新的活动,只需要在RegisterActivity中添加就好啦
总结 : 观察者模式将多个实体列为观察对象,在事件中批量处理对象的触发逻辑,减少搬砖工作量
在蚂蚁森林活动里面,聪明的产品忽然想到,应该来一个大家来种树,4个人一起种一棵树,提高大家的积极性
这样在收取能量的时候要告诉一起种树的人能量被收了,要不然别人还会再次收到能量就不好了
于是又让开发加班加点的设计方案,年轻的开发小哥想到那就要多一个房间的概念
/*ActivityTreeRoom.h*/ class ActivityTreeRoom { private: vector<uint64_t> m_UserIDList;//Room中有哪些人 int RoomID;//Room的ID }; class ActivityTreeRoomMgr { public: static ActivityTreeRoomMgr& Instance() { static ActivityTreeRoomMgr; return s_instance; } //一定有一个user最先开始创建房间 void CreateRoom(User& user); //加入房间 void JoinRoom(User& user, int RoomID); //拉取到Room中的所有人 void GetRoomMember(const vector<uint64_t>& RoomMember, int RoomID); private: vector<ActivityTreeRoom> m_Rooms; }; //为了加快查找,User加入房间后,自己要记录RoomID
再拾能量的时候就需要这么一个函数
/*ActivityTree.cpp*/ void ActivityTree::GetOurEnergy(User& user) { AddEnergy(user); vector<uint64_t> RoomMember; ActivityTreeRoomMgr::Instance().GetRoomMember(RoomMember, user.RoomID); for (uint32_t i = 0; i < RoomMember.size(); ++i) { User& otherUser = UserMgr::Instance().GetUserbyID(RoomMember[i]); //能量已经被捡走了 SendMsgToUser(otherUser); } }
乍一看是没有什么问题,但是ActivityTree为什么要处理Room的逻辑呢。
他们之间并没有直接的联系,如果再加上下线通知、上线通知,内容会越来越多。
中介者模式
其实就是把应该做的活,统统交给Mgr处理就好了
/*ActivityTree.cpp*/ void ActivityTree::GetOurEnergy(User& user) { AddEnergy(user); ActivityTreeRoomMgr::Instance().NotifyMsgToRoomMember(user.RoomID); } /*ActivityTreeRoomMgr.cpp*/ void ActivityTreeRoomMgr::NotifyMsgToRoomMember(int RoomID) { vector<uint64_t> RoomMember; GetRoomMember(RoomMember, user.RoomID); for (uint32_t i = 0; i < RoomMember.size(); ++i) { User& otherUser = UserMgr::Instance().GetUserbyID(RoomMember[i]); SendMsgToUser(otherUser); } }
总结 : 中介者模式是基于两个对象之间不方便直接交流的情况下设定的
《【C++】常用的设计模式实例-单例/装饰器/观察者/中介者》上有1条评论