zoukankan      html  css  js  c++  java
  • 使用消息分发机制降低程序中的耦合度

    扯淡的前言

    响应加班群里轰轰烈烈的“不XX就女装”运动,本人于今日白天立flag如下:

    决定了,今晚写一篇博客,写不出我就女装,出飞行场姬

    于是,特此撰文一篇以拔旗(我这身板出凹凸有致的飞行场姬,那只能用辣眼睛来形容)。

    第一次用MD写博文,MD确实很方便,帮我完成了排版的任务。今后也要继续使用。

    再啰嗦一句,这篇博文使用C#语言做范例。

    真正的前言

    假设有A和B两个窗口,A窗口中有一个按钮,B窗口中有一个文本框。

    现在要求,点击A窗口中的按钮之后,修改B窗口中文本框的内容。

    有很长一段时间,我是用这种方式实现的:

    void Button_Clicked()
    {
        window_B.SetLabelText("23333"); 
    }
    

    其中, Button_Clicked为A窗口的按钮点击回调,window_B为B窗口的实例,SetLabelText为为B窗口类中编写的修改文本的接口。

    这么做乍一看没什么问题,但是如果项目往后走,某一天策划告诉你他需要增加一个窗口C,而C窗口的功能是,在A窗口按钮按下后,在其中播放一段动画。

    那么在完成C窗口类编写后,按钮点击回调的代码就得改了:

    void Button_Clicked()
    {
        window_B.SetLabelText("23333"); 
        window_C.PlayAnimation(); 
    }
    

    再往后走,可能策划会告诉你需要添加窗口DEFG……都得响应窗口A按钮点击操作,而之前写过的窗口C,不需要播放动画了,改为播放音频,等等等……每一次需求的增改,都得去改Button_Clicked的内容。再如果,他说窗口B中要加一个按钮,点了之后的功能要和窗口A按钮相同,那么代码又得复制一次,或者提出来做成全局的……

    “过去一天三遍的吃,麻烦。”

    这个时候,就需要用一个新的设计模式来取代了。

    模式设计

    假如,你是一位记者,你今天需要去采访一位长者,那么应该会出现如下的对话:

    —— 氵主席你觉得董先生连任好不好啊?
    —— 好啊!
    

    这是面对面一对一的采访。

    有一天,领导告诉你,“我们都决定了,你来讲三句话”。你需要把这三句话传达给很多人。

    好了,现在应该怎么做呢?继续面对面一对一肯定是不行的,如果其中一个人不想听了,你的行程就全被打乱了。

    虽然你是香港记者跑得很快,但跑来跑去也很累。

    这个时候,你应该考虑弄个大新闻。

    你把三句话印在报纸上发行出去,这样想知道你三句话内容的,就会去买报纸看,不想看的,对你也没有影响。

    这是把报纸作为中间媒介,用来传递消息。

    我们现在就需要这样一份报纸,替我们完成消息分发的工作。

    于是,现在我们插入一个新的模块M。窗口A按钮点击后,通知模块M,然后模块M把消息分发出去,其他窗口响应这个消息,执行对应的功能。

    那么,这个模块需要有如下三个接口:

    • 注册消息。如果我要响应一个消息,就往模块M中注册这个消息对应的处理回调;
    • 注销消息。如果某个窗口已经被销毁了,而它的处理回调没有被注销,一调用必然就会出错;
    • 分发消息。指定一个消息,将它分发到每个处理器上。

    而在这个模块内部,它的运作流程是这样的:

    注册消息

    • 判断处理回调是否已经被添加,如果已经添加过,则不重复添加

    注销消息

    • 判断处理回调是否已经被添加,如果已经添加过则移除,否则不处理

    分发消息

    • 查询消息是否被注册过,若没有则返回
    • 消息进入消息队列
    • 消息泵推动队列
    • 取出消息
    • 向该消息对应的处理器分发消息,执行处理器对应的功能

    代码编写

    我们给模块M命名为MessageDispatcher。首先,这个模块应该是全局唯一的,所以应当使用单例模式:

    public class MessageDispatcher
    {
        private static MessageDispatcher m_Ins = null;
    
        public static MessageDispatcher Instance
        {
            get
            {
                if (m_Ins == null)
                {
                    m_Ins = new MessageDispatcher();
                }
                return m_Ins;
            }
        }
    
        private MessageDispatcher() { };
    }
    

    也可以使用单例模板,任何需要使用单例模式的类都可以从它派生,减少代码冗余。单例模板请自行百度。

    成员的声明

    这个类中,需要存放被注册的消息处理器,和需要分发的消息队列。所以添加如下两个成员:

    private Dictionary<MessageID, _MessageHandlerCollection> m_HandlerMap;
    private Queue<_Message> m_MessageQueue;
    

    MessageID是一个枚举,表示所有可能出现的消息,目前情况只有窗口A的按钮点击,故只添加一个状态:

    public enum MessageID
    {
    	WindowA_ButtonClicked	
    }
    

    _MessageHandlerCollection是存放在MessageDispatcher中的内部私有类,是消息处理器的集合。由于C#不支持List<event>这种类型的数据,故需要单独编写一个。

    private class _MessageHandlerCollection
    {
        public int Count { get { return this.m_HandlerList.Count; } }
    
        private List<MessageCallback> m_HandlerList;
    
    
        public _MessageHandlerCollection()
        {
            this.m_HandlerList = new List<MessageCallback>();
        }
    
    
        public void AddHandler(MessageCallback pCallback)
        {
            if (!this.m_HandlerList.Contains(pCallback))
            {
                this.m_HandlerList.Add(pCallback);
            }
        }
    
    
        public void RemoveHandler(MessageCallback pCallback)
        {
            this.m_HandlerList.Remove(pCallback);
        }
    
    
        public void DispatchMessage(object pSender, object pParam)
        {
            for (int i = 0, count = this.m_HandlerList.Count; i < count; i++)
            {
                this.m_HandlerList[i].Invoke(pSender, pParam);
            }
        }
    
    
        public void Dispose()
        {
            this.m_HandlerList.Clear();
            this.m_HandlerList = null;
        }
    }
    

    MessageCallback是一个全局的delegate,作用等同于C++中的方法指针。它的声明如下:

    public delegate void MessageCallback(object pSender, object pParam);
    

    _Message是存放在MessageDispatcher中的内部私有类,用于存放需要分发的消息、发送者和参数:

    private class _Message
    {
        public MessageID ID;
        public object Sender;
        public object Param;
    }
    

    接口实现

    成员定义完了,接下来实现前文所说的接口。首先是添加消息处理器的方法:

    public void AddHandler(MessageID pMsgID, MessageCallback pCallback)
    {
        if (pCallback == null)
        {
            return;
        }
    
        if (this.m_HandlerMap.ContainsKey(pMsgID))
        {
            this.m_HandlerMap[pMsgID].AddHandler(pCallback);
        }
        else
        {
            var mhc = new _MessageHandlerCollection();
            mhc.AddHandler(pCallback);
            this.m_HandlerMap.Add(pMsgID, mhc);
        }
    }
    

    然后是移除消息处理器的方法,和上面的很相似:

    public void RemoveHandler(MessageID pMsgID, MessageCallback pCallback)
    {
        if (pCallback == null)
        {
            return;
        }
    
        if (this.m_HandlerMap.ContainsKey(pMsgID))
        {
            var mhc = this.m_HandlerMap[pMsgID];
            mhc.RemoveHandler(pCallback);
    
            if (mhc.Count == 0)
            {
                this.m_HandlerMap.Remove(pMsgID);
            }
        }
    }
    

    需要注意的是如果某个消息的处理器被全部移除了,应当把这个消息从处理器表中移除以释放内存。


    分发消息的方法,收到消息后将消息压入消息队列:

    public void DispatchMessage(MessageID pMsgID, object pSender, object pParam = null)
    {
        if (!this.m_HandlerMap.ContainsKey(pMsgID))
        { 
            return;
        }
    
        var m = new _Message()
        {
            ID = pMsgID,
            Sender = pSender,
            Param = pParam
        };
    
        this.m_MessageQueue.Enqueue(m);
    }
    

    除开前文说的三个接口,还需要另外三个接口:初始化分发器、释放分发器的资源、和推动消息队列。接下来一个一个实现,首先是初始化分发器:

    public void Initialize()
    {
        this.m_HandlerMap = new Dictionary<MessageID, _MessageHandlerCollection>();
        this.m_MessageQueue = new Queue<_Message>();
    }
    

    释放分发器的资源:

    public void Dispose()
    {
        this.m_MessageQueue.Clear();
        this.m_MessageQueue = null;
    
        foreach (var pair in this.m_HandlerMap)
        {
            pair.Value.Dispose();
        }
    
        this.m_HandlerMap.Clear();
        this.m_HandlerMap = null;
    }
    

    推动消息队列。这个方法可以用一个计时器循环调用,也可以用线程(但是需要加上线程锁,还要考虑线程访问主线程控件等的问题)。在我的Unity框架中,这个方法是每帧调用一次的:

    public void Update() 
    {
        if (this.m_MessageQueue == null || this.m_MessageQueue.Count == 0) 
        {
            return;
        }
    
        var msg = this.m_MessageQueue.Dequeue();
        this.m_HandlerMap[msg.ID].DispatchMessage(msg.Sender, msg.Param);
    }
    

    使用消息分发器

    好了,经过编码后,这个消息分发器就可以投入使用了。还是以文章最初的例子来看,此时我们只需要将窗口A的按钮回调改为:

    void Button_Clicked()
    {
        MessageDispatcher.Instance.DispatchMessage(MessageID.WindowA_ButtonClicked, button);
    }
    

    button就是窗口A中按钮的实例。

    对于其他窗口,只需要在窗口中加入如下代码:

    void WindowInitialize()
    {
        MessageDispatcher.Instance.AddHandler(MessageID.WindowA_ButtonClicked, this.OnClicked);
    }
    
    void OnClicked(object pSender, object pParam)
    {
        // TODO: 在这里写每个窗口对应的相应功能代码
    }
    
    void WindowDestroy()
    {
        MessageDispatcher.Instance.RemoveHandler(MessageID.WindowA_ButtonClicked, this.OnClicked);
    }
    

    其中,WindowInitializeWindowDestroy是窗口初始化和销毁的回调,根据所使用的UI框架不同,名字也可能不同。OnClicked则是响应消息的回调。

    如此,不管增加多少个窗口,只需要在每个窗口的代码中这么写一次就行。如果窗口B也需要其他窗口响应,那么在窗口B的按钮回调中调用MessageDispatcher.Instance.DispatchMessage就行了,简单粗暴。

    后记

    这个模块的设计思维就是,我只用广播我做了啥,至于广播发出去之后你爱咋咋地,我就不管了。通过这种方式,两个模块之间的耦合度得到了降低。

    这个模块目前有一个缺陷,就是对多线程支持不好,代码中可以看出是没有加线程锁的。如果你需要在多线程环境下使用该模块,请自行添加它。

    那么总算赶在12点前拔掉了旗,不用女装了。但是心中有一些微微的失落感是怎么回事(喂!

    最后附上完整代码(直接从项目里面复制出来的,有些地方需要小修改,比如继承的BaseManager<T>

    最后的最后,希望两个月内写完TooSimple Framework,目前我对它的定位是一个基于Unity的资瓷热更新的框架,集成了很多常用的功能组件,希望所有的用户拿着它就可以开工写项目。写完之后会开源的,努力吧~

    //————————————————————————————————————————————
    //  MessageManager.cs
    //  For project: TooSimple Framework
    //
    //  Created by Chiyu Ren on 2016-06-11 21:38
    //————————————————————————————————————————————
    
    using System.Collections.Generic;
    
    using TooSimpleFramework.Common;
    
    
    namespace TooSimpleFramework.Components.Managers
    {
        /// <summary>
        /// 消息管理器,用于分发消息
        /// </summary>
        public class MessageManager : BaseManager<MessageManager>
        {
            #region Private Members
            private Dictionary<MessageID, _MessageHandlerCollection> m_HandlerMap;
            private Queue<_Message> m_MessageQueue;
            #endregion
    
    
            #region Public Methods
            /// <summary>
            /// 初始化消息管理器
            /// </summary>
            public override void Initialize()
            {
                this.m_HandlerMap = new Dictionary<MessageID, _MessageHandlerCollection>();
                this.m_MessageQueue = new Queue<_Message>();
            }
    
            /// <summary>
            /// 推动消息队列
            /// </summary>
            public override void Update()
            {
                if (this.m_MessageQueue == null || this.m_MessageQueue.Count == 0)
                {
                    return;
                }
    
                var msg = this.m_MessageQueue.Dequeue();
                this.m_HandlerMap[msg.ID].DispatchMessage(msg.Sender, msg.Param);
            }
    
            /// <summary>
            /// 释放消息管理器的资源
            /// </summary>
            public override void Dispose()
            {
                this.m_MessageQueue.Clear();
                this.m_MessageQueue = null;
    
                foreach (var pair in this.m_HandlerMap)
                {
                    pair.Value.Dispose();
                }
    
                this.m_HandlerMap.Clear();
                this.m_HandlerMap = null;
            }
    
            /// <summary>
            /// 添加一个消息接收器
            /// </summary>
            /// <param name="pMsgID">消息ID</param>
            /// <param name="pCallback">接受到消息的回调</param>
            public void AddHandler(MessageID pMsgID, MessageCallback pCallback)
            {
                if (pCallback == null)
                {
                    return;
                }
    
                if (this.m_HandlerMap.ContainsKey(pMsgID))
                {
                    this.m_HandlerMap[pMsgID].AddHandler(pCallback);
                }
                else
                {
                    var mhc = new _MessageHandlerCollection();
                    mhc.AddHandler(pCallback);
                    this.m_HandlerMap.Add(pMsgID, mhc);
                }
            }
    
            /// <summary>
            /// 移除指定消息接收器
            /// </summary>
            /// <param name="pMsgID">消息ID</param>
            /// <param name="pCallback">添加时的回调</param>
            public void RemoveHandler(MessageID pMsgID, MessageCallback pCallback)
            {
                if (pCallback == null)
                {
                    return;
                }
    
                if (this.m_HandlerMap.ContainsKey(pMsgID))
                {
                    var mhc = this.m_HandlerMap[pMsgID];
                    mhc.RemoveHandler(pCallback);
    
                    if (mhc.Count == 0)
                    {
                        this.m_HandlerMap.Remove(pMsgID);
                    }
                }
            }
    
            /// <summary>
            /// 向所有注册的接收器分发指定消息
            /// </summary>
            /// <param name="pMsgID">消息ID</param>
            /// <param name="pSender">消息发送者</param>
            /// <param name="pParam">消息参数</param>
            public void DispatchMessage(MessageID pMsgID, object pSender, object pParam = null)
            {
                if (!this.m_HandlerMap.ContainsKey(pMsgID))
                { 
                    return;
                }
    
                var m = new _Message()
                {
                    ID = pMsgID,
                    Sender = pSender,
                    Param = pParam
                };
    
                this.m_MessageQueue.Enqueue(m);
            }
            #endregion
    
    
            private class _Message
            {
                public MessageID ID;
                public object Sender;
                public object Param;
            }
    
    
            private class _MessageHandlerCollection
            {
                public int Count { get { return this.m_HandlerList.Count; } }
    
                private List<MessageCallback> m_HandlerList;
    
    
                public _MessageHandlerCollection()
                {
                    this.m_HandlerList = new List<MessageCallback>();
                }
    
    
                public void AddHandler(MessageCallback pCallback)
                {
                    if (!this.m_HandlerList.Contains(pCallback))
                    {
                        this.m_HandlerList.Add(pCallback);
                    }
                }
    
    
                public void RemoveHandler(MessageCallback pCallback)
                {
                    this.m_HandlerList.Remove(pCallback);
                }
    
    
                public void DispatchMessage(object pSender, object pParam)
                {
                    for (int i = 0, count = this.m_HandlerList.Count; i < count; i++)
                    {
                        this.m_HandlerList[i].Invoke(pSender, pParam);
                    }
                }
    
    
                public void Dispose()
                {
                    this.m_HandlerList.Clear();
                    this.m_HandlerList = null;
                }
            }
        }
    
    
        public delegate void MessageCallback(object pSender, object pParam);
    }
    

    为什么这个框架要叫TooSimple?刚才你问我,我可以回答你一句无可奉告,但你又不高兴,那我怎么办?

    很惭愧,就做了一点微小的工作,谢谢大家!

  • 相关阅读:
    [Android开发]cocos2dx工程中接入支付宝sdk
    cocos2dx android SDK接入总结
    Cocos2d-vs避免过长编译的小技巧
    math.h里的数学计算公式介绍
    Cocos Code IDE入门指南
    kbengine引擎的安装
    游戏服务端pomelo完整安装配置过程
    cocos2d-x 两个场景间进行传参数
    cocos2d-x 设置全局可变变量
    如何将cocos2d-x项目打包成一个.exe
  • 原文地址:https://www.cnblogs.com/GuyaWeiren/p/5683188.html
Copyright © 2011-2022 走看看