观察者模式

前言 

这篇文章讲述消息监听方案的发展史,来阐述观察者模式的优点。本篇文章的故事纯属虚构,如有雷同只是巧合,前排拿好瓜子和板凳准备开始了。

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. 玩家的游戏人物状态更新时,需要通知其他客户端玩家同步更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值