【C++】常用的设计模式实例-单例/装饰器/观察者/中介者
以一个实际的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);
}
}
总结 : 中介者模式是基于两个对象之间不方便直接交流的情况下设定的
What's Halpening i'm nnew to this, I tumbled upon thi I
hawve discovered It positively useful and it haas aided me outt
loads. I aam hopinhg to giive a contribution & help different users like itts aided me.
Greeat job.
Whhat a information oof un-ambiguity and preservwness
of precioys experienche concerning unexpected feelings.