天天看點

開發程式設計小技巧(1):對代碼解耦合一. 使用事件消息的廣播和監聽

技巧不意味着“奇技淫巧”,而是一種通過某些方法和手段來讓開發過程更加容易,開發内容更加直覺的方式。在開發過程中,多多使用技巧而不是一味的生搬硬套是值得提倡,但是若盲目追求技巧的使用而不顧代碼的健壯性,那就得不償失了。(不定期更新)

附:下面内容配套使用的軟體為Unity,但技巧是通用的。

将各個功能進行解耦合開發,是從初級程式員走向中級程式員的标志。是以,深入了解解耦合的方法,能在開發過程中自然而然地使用出來,是一個優秀程式員的基本素養。

一. 使用事件消息的廣播和監聽

使用事件的廣播和監聽,需要對委托有所了解,這裡推薦一篇講C#中委托比較好的文章。同時也需要了解一下靜态類。

該方法的優勢也是比較明顯的,廣播者不需要關心監聽者是誰,監聽者也不需要關心廣播者,每個代碼隻需要關心自己的事情。

首先寫好消息管理類

// 定義委托類型,委托相當于定義了一個類似int的資料類型,使用函數為其指派
public delegate void Callback();
public delegate void Callback<T>(T arg);

// 定義事件類型,監聽者隻對特定的事件類型進行監聽
//(當然也是可以不定類型而對所有事件進行監聽,但這樣效率低而且很亂)
public enum EventType{
	ExampleType1,
	ExampleType2
}

// 定義事件消息管理中心,提供添加監聽和進行廣播的方法
public static EventCenter{
	private static Dictionary<EventType, Delegate> m_eventDic = new Dictionary<EventType, Delegate>();
	// 添加監聽者的方法(對無參Callback類型的添加)
	public static void AddMonitor(EventType eventType, Callback callback)
	{
		OnAddMonitor(eventType, callback);
		m_eventDic[eventType] = (Callback)m_eventDic[eventType] + callback;	// 相當于調用了Delegate類的Combine方法
	}
	// 添加監聽者的方法(對單個泛型Callback類型的添加,後面多個的以此類推)
	public static void AddMonitor<T>(EventType eventType, Callback<T> callback)
	{
		OnAddMonitor(eventType, callback);
		m_eventDic[eventType] = (Callback<T>)m_eventDic[eventType] + callback;
	}
	// 移除監聽者的方法(對無參Callback類型的移除)
	public static void RemoveMonitor(EventType eventType, Callback callback){
		OnRemoveMonitor(eventType, callback);
		m_eventDic[eventType] = (Callback)m_eventDic[eventType] - callback;
		OnRemoveEventType(eventType);
	}
	// 移除監聽者的方法(對單個泛型Callback類型的移除,後面多個的以此類推)
	public static void RemoveMonitor<T>(EventType eventType, Callback<T> callback){
		OnRemoveMonitor(eventType, callback);
		m_eventDic[eventType] = (Callback<T>)m_eventDic[eventType] - callback;
		OnRemoveEventType(eventType);
	}
	// 廣播事件消息(對無參Callback類型)
	public static void Broadcast(EventType eventType)
	{
		Delegate temDel;
		if (m_eventDic.TryGetValue(eventType, out temDel))
		{
			Callback callback = temDel as Callback;
			if (callback != null)
				callback();
			else 
				throw new Exception(string.Format("廣播事件錯誤:事件{0}中委托為空或具有不同類型委托;", eventType));
		}
	}
	// 廣播事件消息(對單個泛型Callback類型,後面多個的以此類推)
	public static void Broadcast<T>(EventType eventType, T arg)
	{
		Delegate temDel;
		if (m_eventDic.TryGetValue(eventType, out temDel))
		{
			Callback<T> callback = temDel as Callback<T>;
			if (callback != null)
				callback(arg);
			else 
				throw new Exception(string.Format("廣播事件錯誤:事件{0}中委托為空或具有不同類型委托;", eventType));
		}
	}

	private static void OnAddMonitor(EventType eventType, Delegate callback)
	{
		if (!m_eventDic.ContainsKey(eventType))
			m_eventDic.Add(eventType, null);
		Delegate temDel = m_eventDic[eventType];
		if (temDel != null && temDel.GetType() != callback.GetType())
			throw new Exception(string.Format("添加監聽錯誤:嘗試為事件{0}添加委托,但目前事件委托{1}與要添加的委托類型{2}不同;", eventType, temDel.GetType(), callback.GetType());
	}
	private static void OnRemoveMonitor(EventType eventType, Delegate call)
	{
		if (m_eventDic.ContainsKey(eventType))
		{
			Delegate temDel = m_eventDic[eventType];
			if (temDel == null)
				throw new Exception(string.Format("移除監聽錯誤:事件{0}沒有對應委托;", eventType));
			else if (temDel.GetType() != callback.GetType())
				throw new Exception(string.Format("移除監聽錯誤:嘗試為事件{0}移除委托,但目前事件委托{1}與要移除的委托類型{2}不同;", eventType, temDel.GetType(), callback.GetType());
		}
		else 
			throw new Exception(string.Format("移除監聽錯誤:并沒有該事件碼{0};", eventType));
	}
	private static void OnRemoveEventType(EventType eventType){
		if (m_eventDic[eventType] == null)
			m_eventDic.Remove(eventType);
	}
}
           

下面的監聽者隻需要利用寫好的消息類就可以很友善的對廣播者的某個事件進行回報,而且并不會因為缺少某個角色而報錯(沒有廣播者或監聽者并不影響)。

// 監聽者
public class Monitor : MonoBehaviour{
	void Awake(){
		EventCenter.AddMonitor<int>(EventType.ExampleTest1, Test);
	}

	void OnDestroy(){
		EventCenter.RemoveMonitor<int>(EventType.ExampleTest1, Test);
	}
	
	private void Test(int num){
		Debug.Log("我被調用啦" + num);
	}
}

// 廣播者
public class Broadcaster{
	void Start(){
		// 這裡也展現了這種方法的劣勢——廣播時傳入的參數必須和監聽者對應事件的方法的參數類型一緻,
		// 但這裡并不能直接知道對應事件的參數類型,隻能通過查找那個事件或者寫說明文檔來解決。
		EventCenter.Broadcast(EventType.ExampleTest1, 2);
	}
}