框架之消息机制(通知系统,观察者模式)

本篇我们实现一种消息机制。为什么需要消息机制,很简单,解耦合。

举个例子,游戏里面当资源数量更新时(例如粮食+200),所有显示该资源数量的界面都需要更新该资源的数量文本(例如训练士兵、升级建筑、治疗、研发等等),这可能会涉及十几种界面,而只有打开的界面需要更新。

那么当客户端收到服务端的数量更新消息时,在逻辑类里一个个的判断界面是否打开,如果界面打开则调用界面的更新方法显然是很低效、耦合的。那么消息机制的实现方式是怎么样的呢?

界面在打开时监听一条事件RESOURCE_DATA_UPDATE,在关闭时移除该事件RESOURCE_DATA_UPDATE,逻辑类收到资源更新消息时会触发这个事件,这样每个监听该事件的界面都可以收到资源更新的通知。

具体的实现是通过事件类型EventType 和c#委托Delegate实现的,EventType是自己定义的枚举。关键代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();
public delegate void CallBack();

//添加一个事件(例如界面打开时)
public static void AddListener(EventType eventType, CallBack callBack)
{
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack;
}

//移除一个事件(例如界面关闭时)
public static void RemoveListener(EventType eventType, CallBack callBack)
{
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}

//触发事件,逻辑类调用
public static void Broadcast(EventType eventType)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack callBack = d as CallBack;
if (callBack != null)
{
callBack();
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}

详细代码如下:

一、c#端

1.回调定义:原本打算使用params object[] param作为回调参数,这样只需要定义一个回调。但是这种做法会导致频繁的装箱、拆箱操作,而且业务的代码也不好写。

装箱:值类型转引用类型,例如int装object。对值类型进行装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。 最后会返回指向该内存的指针。我们应该尽量避免装箱操作。

1
2
3
4
5
6
7
public delegate void CallBack();
public delegate void CallBack<T>(T arg);
public delegate void CallBack<T, X>(T arg1, X arg2);
public delegate void CallBack<T, X, Y>(T arg1, X arg2, Y arg3);
public delegate void CallBack<T, X, Y, Z>(T arg1, X arg2, Y arg3, Z arg4);
public delegate void CallBack<T, X, Y, Z, W>(T arg1, X arg2, Y arg3, Z arg4, W arg5);
//public delegate void CallBack(params object[] param);

2.事件定义:

public enum EventType
{
ShowText,
}
3.事件添加、移除、分发:最多支持五个参数的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
public class EventCenter
{
private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();

private static void OnListenerAdding(EventType eventType, Delegate callBack)
{
if (!m_EventTable.ContainsKey(eventType))
{
m_EventTable.Add(eventType, null);
}
Delegate d = m_EventTable[eventType];
if (d != null && d.GetType() != callBack.GetType())
{
throw new Exception(string.Format("尝试为事件{0}添加不同类型的委托,当前事件所对应的委托是{1},要添加的委托类型为{2}", eventType, d.GetType(), callBack.GetType()));
}
}
private static void OnListenerRemoving(EventType eventType, Delegate callBack)
{
if (m_EventTable.ContainsKey(eventType))
{
Delegate d = m_EventTable[eventType];
if (d == null)
{
throw new Exception(string.Format("移除监听错误:事件{0}没有对应的委托", eventType));
}
else if (d.GetType() != callBack.GetType())
{
throw new Exception(string.Format("移除监听错误:尝试为事件{0}移除不同类型的委托,当前委托类型为{1},要移除的委托类型为{2}", eventType, d.GetType(), callBack.GetType()));
}
}
else
{
throw new Exception(string.Format("移除监听错误:没有事件码{0}", eventType));
}
}
private static void OnListenerRemoved(EventType eventType)
{
if (m_EventTable[eventType] == null)
{
m_EventTable.Remove(eventType);
}
}
//no parameters
public static void AddListener(EventType eventType, CallBack callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack;
}
//Single parameters
public static void AddListener<T>(EventType eventType, CallBack<T> callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] + callBack;
}
//two parameters
public static void AddListener<T, X>(EventType eventType, CallBack<T, X> callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] + callBack;
}
//three parameters
public static void AddListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] + callBack;
}
//four parameters
public static void AddListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] + callBack;
}
//five parameters
public static void AddListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack)
{
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] + callBack;
}

//no parameters
public static void RemoveListener(EventType eventType, CallBack callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}
//single parameters
public static void RemoveListener<T>(EventType eventType, CallBack<T> callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}
//two parameters
public static void RemoveListener<T, X>(EventType eventType, CallBack<T, X> callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}
//three parameters
public static void RemoveListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}
//four parameters
public static void RemoveListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}
//five parameters
public static void RemoveListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack)
{
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
}


//no parameters
public static void Broadcast(EventType eventType)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack callBack = d as CallBack;
if (callBack != null)
{
callBack();
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
//single parameters
public static void Broadcast<T>(EventType eventType, T arg)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack<T> callBack = d as CallBack<T>;
if (callBack != null)
{
callBack(arg);
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
//two parameters
public static void Broadcast<T, X>(EventType eventType, T arg1, X arg2)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack<T, X> callBack = d as CallBack<T, X>;
if (callBack != null)
{
callBack(arg1, arg2);
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
//three parameters
public static void Broadcast<T, X, Y>(EventType eventType, T arg1, X arg2, Y arg3)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack<T, X, Y> callBack = d as CallBack<T, X, Y>;
if (callBack != null)
{
callBack(arg1, arg2, arg3);
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
//four parameters
public static void Broadcast<T, X, Y, Z>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack<T, X, Y, Z> callBack = d as CallBack<T, X, Y, Z>;
if (callBack != null)
{
callBack(arg1, arg2, arg3, arg4);
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
//five parameters
public static void Broadcast<T, X, Y, Z, W>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4, W arg5)
{
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
{
CallBack<T, X, Y, Z, W> callBack = d as CallBack<T, X, Y, Z, W>;
if (callBack != null)
{
callBack(arg1, arg2, arg3, arg4, arg5);
}
else
{
throw new Exception(string.Format("广播事件错误:事件{0}对应委托具有不同的类型", eventType));
}
}
}
}

二、lua端,lua对调并不需要定义。

1.事件定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
-- 广播消息定义
local BroadcastMsgConfig = {
ACTIVITY_INIT = "ACTIVITY_INIT", -- 活动初始化
ACTIVITY_EXPIRE = "ACTIVITY_EXPIRE", -- 活动过期
ACTIVITY_REFRESH = "ACTIVITY_REFRESH", -- 活动刷新
ACTIVITY_ENTER = "ACTIVITY_ENTER", -- 进入活动
ACTIVITY_PLAY = "ACTIVITY_PLAY", -- 玩活动
...
}

-- 广播消息中心管理
local BroadcastCenter = BaseClass("BroadcastCenter", Singleton)
local Messenger = require "Framework.Common.Messenger"

-- 构造函数
function BroadcastCenter.__init(cls_obj)
cls_obj.msgRegister = Messenger.New() -- 广播消息注册器
end

-- 析构函数
function BroadcastCenter.__delete(cls_obj)
cls_obj.msgRegister:Delete()
cls_obj.msgRegister = nil
end

-- 注册消息
function BroadcastCenter:AddListener(msg_type, msg_listener, ...)
self.msgRegister:AddListener(msg_type, msg_listener, ...)
end

-- 注销消息
function BroadcastCenter:RemoveListener(msg_type, msg_listener)
if self.msgRegister then
self.msgRegister:RemoveListener(msg_type, msg_listener)
end
end

-- 发送消息
function BroadcastCenter:Broadcast(msg_type, ...)
if self.msgRegister then
self.msgRegister:Broadcast(msg_type, ...)
end
end

return BroadcastCenter

local Messenger = BaseClass("Messenger");

function Messenger.__init(cls_obj)
cls_obj.events = {}
end

function Messenger.__delete(cls_obj)
cls_obj.events = nil
cls_obj.error_handle = nil
end

function Messenger.AddListener(cls_obj, e_type, e_listener, ...)
local event = cls_obj.events[e_type]
if event == nil then
event = setmetatable({}, {__mode = "k"})
end

for k, v in pairs(event) do
if k == e_listener then
error("Aready cotains listener : "..tostring(e_listener))
return
end
end

event[e_listener] = setmetatable(SafePack(...), {__mode = "kv"})
cls_obj.events[e_type] = event;
end

function Messenger.Broadcast(cls_obj, e_type, ...)
local event = cls_obj.events[e_type]
if event == nil then
return
end

for k, v in pairs(event) do
assert(k ~= nil)
local args = ConcatSafePack(v, SafePack(...))
k(SafeUnpack(args))
end
end

function Messenger.RemoveListener(cls_obj, e_type, e_listener)
local event = cls_obj.events[e_type]
if event == nil then
return
end

event[e_listener] = nil
end

function Messenger.RemoveListenerByType(cls_obj, e_type)
cls_obj.events[e_type] = nil
end

function Messenger.Cleanup(cls_obj)
cls_obj.events = {};
end

return Messenger

注意:
1、模块实例销毁时,要自动移除消息监听,不移除的话不能自动清理监听
2、使用弱引用,即使监听不手动移除,消息系统也不会持有对象引用,所以对象的销毁是不受消息系统影响的
3、换句话说:广播发出,回调一定会被调用,但回调参数中的实例对象,可能已经被销毁,所以回调函数一定要注意判空