扯淡的前言
响应加班群里轰轰烈烈的“不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);
}
其中,WindowInitialize
和WindowDestroy
是窗口初始化和销毁的回调,根据所使用的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?刚才你问我,我可以回答你一句无可奉告,但你又不高兴,那我怎么办?
很惭愧,就做了一点微小的工作,谢谢大家!