前言
这篇文章讲述消息监听方案的发展史,来阐述观察者模式的优点。本篇文章的故事纯属虚构,如有雷同只是巧合,前排拿好瓜子和板凳准备开始了。
1.很久以前的消息监听方案
记得很久很久以前,一群程序员准备构建游戏网络模块的框架时,思考了良久,他们终于决定把全部监听消息的操作都集中在SocketManager类里,相当于使用中介者模式去实现消息分拨功能,然后他们满意的点点头开始写代码了,具体代码如下:
public enum CsProtocol:uint
{
cs_login_result = 1,
cs_register_result = 2,
cs_exchange_result = 3,
cs_chat_result = 4,
cs_purchase_result = 5,
}
public struct GmMessage
{
public CsProtocol Protocol;//协议
public byte Code;//状态码
public uint oParam;//扩展参数1
public uint tParam;//扩展参数2
public byte[] Binary;//字节流
}
public class LoginUIForm : UIForm
{
void Awake()
{
Globe.LoginUiForm = this;
}
void OnDestroy()
{
Globe.LoginUiForm = null;
}
public void ReceiveLoginSuccess(GmMessage gmMessage)
{
Console.WriteLine("登陆模块成功消息");
}
public void ReceiveLoginFailed(GmMessage gmMessage)
{
Console.WriteLine("登陆模块失败消息");
}
}
public class RegisterUIForm : UIForm
{
void Awake()
{
Globe.RegisterUiForm = this;
}
void OnDestroy()
{
Globe.RegisterUiForm = null;
}
public void ReceiveRegisterSuccess(GmMessage gmMessage)
{
Console.WriteLine("注册模块成功消息");
}
public void ReceiveRegisterFailed(GmMessage gmMessage)
{
Console.WriteLine("注册模块失败消息");
}
}
public class GameShopUIForm : UIForm
{
void Awake()
{
Globe.GameShopUiForm = this;
}
void OnDestroy()
{
Globe.GameShopUiForm = null;
}
public void ReceivePurchaseSuccess(GmMessage gmMessage)
{
Console.WriteLine("购买模块成功消息");
}
public void ReceivePurchaseFailed(GmMessage gmMessage)
{
Console.WriteLine("购买模块失败消息");
}
}
public class ChatRoomUIForm : UIForm
{
void Awake()
{
Globe.ChatRoomUiForm = this;
}
void OnDestroy()
{
Globe.ChatRoomUiForm = null;
}
public void ReceiveChatResut(GmMessage gmMessage)
{
Console.WriteLine("聊天模块消息");
}
}
public class ExchangeGoodsUIForm : UIForm
{
void Awake()
{
Globe.ExchangeGoodsUiForm = this;
}
void OnDestroy()
{
Globe.ExchangeGoodsUiForm = null;
}
public void ReceiveExchangeSuccess(GmMessage gmMessage)
{
Console.WriteLine("兑换模块成功消息");
}
public void ReceiveExchangeFailed(GmMessage gmMessage)
{
Console.WriteLine("兑换模块失败消息");
}
}
public static class Globe
{
public static LoginUIForm LoginUiForm = null;
public static RegisterUIForm RegisterUiForm = null;
public static GameShopUIForm GameShopUiForm = null;
public static ChatRoomUIForm ChatRoomUiForm = null;
public static ExchangeGoodsUIForm ExchangeGoodsUiForm = null;
}
public class SocketManager
{
public static readonly SocketManager instance = new SocketManager();
public void ReceiveMessage(GmMessage gmMessage)//消息监听主入口
{
switch (gmMessage.Protocol)
{
case CsProtocol.cs_chat_result:
Globe.ChatRoomUiForm?.ReceiveChatResut(gmMessage);
break;
case CsProtocol.cs_exchange_result:
if(gmMessage.Code == 0)
Globe.ExchangeGoodsUiForm?.ReceiveExchangeFailed(gmMessage);
else if(gmMessage.Code == 1)
Globe.ExchangeGoodsUiForm?.ReceiveExchangeSuccess(gmMessage);
break;
case CsProtocol.cs_login_result:
if (gmMessage.Code == 0)
Globe.LoginUiForm?.ReceiveLoginFailed(gmMessage);
else if (gmMessage.Code == 1)
Globe.LoginUiForm?.ReceiveLoginSuccess(gmMessage);
break;
case CsProtocol.cs_purchase_result:
if (gmMessage.Code == 0)
Globe.GameShopUiForm?.ReceivePurchaseFailed(gmMessage);
else if (gmMessage.Code == 1)
Globe.GameShopUiForm?.ReceivePurchaseSuccess(gmMessage);
break;
case CsProtocol.cs_register_result:
if (gmMessage.Code == 0)
Globe.RegisterUiForm?.ReceiveRegisterFailed(gmMessage);
else if (gmMessage.Code == 1)
Globe.RegisterUiForm?.ReceiveRegisterSuccess(gmMessage);
break;
}
}
}
可是随着项目越来越大,消息管理器里代码越来越多,看上去越来越乱,而且每次有新的消息都要改动消息管理器。终于有个程序员实在看不下去了,他看到这里的代码就像大碗面一样的又长又宽,实在忍无可忍了。俗话说得好不在沉默中爆发就在沉默中灭亡。
有些人忍着忍着一辈子就过去,很明显今天的主角不是这样的人。于是乎,他怀着雄心壮志准备把这里的代码换个设计模式,想到使用观察者模式将消息分拨改成消息监听的方式。
2.观察者模式重构消息模块
准备将消息协议当作Key,Value是具有接受消息功能的对象,这样字典的key-value就思考清楚了,想要监听协议时就去注册什么协议,他心里美滋滋的开始写起了代码,具体代码如下:
public enum CsProtocol:uint
{
cs_login_result = 1,
cs_register_result = 2,
cs_exchange_result = 3,
cs_chat_result = 4,
cs_purchase_result = 5,
}
public struct GmMessage
{
public CsProtocol Protocol;//协议
public byte Code;//状态码
public uint oParam;//扩展参数1
public uint tParam;//扩展参数2
public byte[] Binary;//字节流
}
public interface INetModule
{
void ReceiveMessage(GmMessage gmMessage);
}
public class LoginUIForm : INetModule, UIForm
{
void Awake()
{
Globe.LoginUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_login_result,this);
}
void OnDestroy()
{
Globe.LoginUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_login_result);
}
public void ReceiveMessage(GmMessage gmMessage)
{
if (gmMessage.Code == 0)//失败
{
Console.WriteLine("登陆失败");
}
else if (gmMessage.Code == 1)//成功
{
Console.WriteLine("登陆成功");
}
}
}
public class RegisterUIForm : INetModule, UIForm
{
void Awake()
{
Globe.RegisterUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_register_result,this);
}
void OnDestroy()
{
Globe.RegisterUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_register_result);
}
public void ReceiveMessage(GmMessage gmMessage)
{
if (gmMessage.Code == 0)//失败
{
Console.WriteLine("注册失败");
}
else if (gmMessage.Code == 1)//成功
{
Console.WriteLine("注册成功");
}
}
}
public class GameShopUIForm : INetModule, UIForm
{
void Awake()
{
Globe.GameShopUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_purchase_result,this);
}
void OnDestroy()
{
Globe.GameShopUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_purchase_result);
}
public void ReceiveMessage(GmMessage gmMessage)
{
if (gmMessage.Code == 0)//失败
{
Console.WriteLine("购买失败");
}
else if (gmMessage.Code == 1)//成功
{
Console.WriteLine("购买成功");
}
}
}
public class ChatRoomUIForm : INetModule, UIForm
{
void Awake()
{
Globe.ChatRoomUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_chat_result, this);
}
void OnDestroy()
{
Globe.ChatRoomUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_chat_result);
}
public void ReceiveMessage(GmMessage gmMessage)
{
Console.WriteLine("聊天消息显示");
}
}
public class ExchangeGoodsUIForm : INetModule, UIForm
{
void Awake()
{
Globe.ExchangeGoodsUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_exchange_result,this);
}
void OnDestroy()
{
Globe.ExchangeGoodsUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_exchange_result);
}
public void ReceiveMessage(GmMessage gmMessage)
{
if (gmMessage.Code == 0)//失败
{
Console.WriteLine("兑换失败");
}
else if (gmMessage.Code == 1)//成功
{
Console.WriteLine("兑换成功");
}
}
}
public static class Globe
{
public static LoginUIForm LoginUiForm = null;
public static RegisterUIForm RegisterUiForm = null;
public static GameShopUIForm GameShopUiForm = null;
public static ChatRoomUIForm ChatRoomUiForm = null;
public static ExchangeGoodsUIForm ExchangeGoodsUiForm = null;
}
public class SocketManager
{
public static readonly SocketManager instance = new SocketManager();
public static Dictionary<CsProtocol,INetModule> MessageProcessor
= new Dictionary<CsProtocol, INetModule>();
public void ReceiveMessage(GmMessage gmMessage)//消息监听主入口
{
try
{
INetModule netModule = null;
if(MessageProcessor.TryGetValue(gmMessage.Protocol, out netModule))
netModule.ReceiveMessage(gmMessage);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public void AddListener(CsProtocol csProtocol,INetModule netModule)
{
if(!MessageProcessor.ContainsKey(csProtocol))
MessageProcessor.Add(csProtocol,netModule);
else
Console.WriteLine("已经注册相关协议,请勿重复");
}
public void RemoveListener(CsProtocol csProtocol)
{
if (!MessageProcessor.Remove(csProtocol))
Console.WriteLine("移除失败,没有存在此协议");
}
}
写完以后,大家刚开始用着还是挺开心,SocketManager里的代码也简单清晰了很多,所以项目组的人都夸他写的这个很棒,但是有一个程序员开始反驳他了,说他连这个天才的设计都可以想到为何不把value改成委托岂不是美哉?他仔细思考了一下,确实说的很有道理啊,为什么我不把字典的value改成委托。于是乎,他又开始重构代码。
3.字典的value改成委托
他思考了一下,同事说的这个方案确实优秀,不用每个需要接受消息的类去继承INetModule接口,并且接受消息的函数可以随意命名,虽然只是小小的改动,好处确实多多。于是他又又开始调整代码,具体代码如下:
public enum CsProtocol : uint
{
cs_login_result = 1,
cs_register_result = 2,
cs_exchange_result = 3,
cs_chat_result = 4,
cs_purchase_result = 5,
}
public struct GmMessage
{
public CsProtocol Protocol;//协议
public byte Code;//状态码
public uint oParam;//扩展参数1
public uint tParam;//扩展参数2
public byte[] Binary;//字节流
}
public class LoginUIForm : UIForm
{
void Awake()
{
Globe.LoginUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_login_result,ReceiveLoginResult);
}
void OnDestroy()
{
Globe.LoginUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_login_result);
}
public void ReceiveLoginResult(GmMessage gmMessage)
{
Console.WriteLine("登陆模块消息");
}
}
public class RegisterUIForm : UIForm
{
void Awake()
{
Globe.RegisterUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_register_result,ReceiveRegisterResult);
}
void OnDestroy()
{
Globe.RegisterUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_register_result);
}
public void ReceiveRegisterResult(GmMessage gmMessage)
{
Console.WriteLine("注册模块消息");
}
}
public class GameShopUIForm : UIForm
{
void Awake()
{
Globe.GameShopUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_purchase_result,ReceivePurchaseResult);
}
void OnDestroy()
{
Globe.GameShopUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_purchase_result);
}
public void ReceivePurchaseResult(GmMessage gmMessage)
{
Console.WriteLine("购买模块消息");
}
}
public class ChatRoomUIForm : UIForm
{
void Awake()
{
Globe.ChatRoomUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_chat_result,ReceiveChatResut);
}
void OnDestroy()
{
Globe.ChatRoomUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_chat_result);
}
public void ReceiveChatResut(GmMessage gmMessage)
{
Console.WriteLine("聊天模块消息");
}
}
public class ExchangeGoodsUIForm : UIForm
{
void Awake()
{
Globe.ExchangeGoodsUiForm = this;
SocketManager.instance.AddListener(CsProtocol.cs_exchange_result,ReceiveExchangeResult);
}
void OnDestroy()
{
Globe.ExchangeGoodsUiForm = null;
SocketManager.instance.RemoveListener(CsProtocol.cs_exchange_result);
}
public void ReceiveExchangeResult(GmMessage gmMessage)
{
Console.WriteLine("兑换模块消息");
}
}
public static class Globe
{
public static LoginUIForm LoginUiForm = null;
public static RegisterUIForm RegisterUiForm = null;
public static GameShopUIForm GameShopUiForm = null;
public static ChatRoomUIForm ChatRoomUiForm = null;
public static ExchangeGoodsUIForm ExchangeGoodsUiForm = null;
}
public class SocketManager
{
public static readonly SocketManager instance = new SocketManager();
public static Dictionary<CsProtocol,Action<GmMessage>> MessageProcessor
= new Dictionary<CsProtocol, Action<GmMessage>>();
public void ReceiveMessage(GmMessage gmMessage)//消息监听主入口
{
try
{
Action<GmMessage> netFun = null;
if(MessageProcessor.TryGetValue(gmMessage.Protocol, out netFun))
netFun?.Invoke(gmMessage);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public void AddListener(CsProtocol csProtocol, Action<GmMessage> netFun)
{
if(!MessageProcessor.ContainsKey(csProtocol))
MessageProcessor.Add(csProtocol, netFun);
else
Console.WriteLine("已经注册相关协议,请勿重复");
}
public void RemoveListener(CsProtocol csProtocol)
{
if (!MessageProcessor.Remove(csProtocol))
Console.WriteLine("移除失败,没有存在此协议");
}
}
到这里就是最佳的消息监听的方案了。此程序员内心os:我简直是一个天才可以想到这种设计方式去实现消息监听,升职加薪、迎娶白富美岂不是指日可待🧐。
总结
1.观察者模式优缺点
优点:1、可以减少通知类过于庞大,建立了一套触发机制。 2、观察者模式支持广播通讯,被观察者会向所有的注册过的观察者发出通知。
缺点:1、如果过多注册被观察对象,可能导致性能问题(因为多了字典的查询或者是循环)。2、如果被观察者之间有循环依赖的话,被观察者会触发它们之间的循环调用,导致系统崩溃。
2.观察者模式使用场景
1.网络模块开发时的消息监听方案
2. 玩家的游戏人物状态更新时,需要通知其他客户端玩家同步更新。