C#設計模式(一)訂閱-釋出模式

犀利叔藍波發表於2016-07-01

本來是在持續開發Besige,但考慮到以後遲早會在設計模式這塊繼續擴充,就新開了一個標題,算是個開個頭。

模式這東西原本可以獨立於語言之外來說,但考慮到不同語言的實現畢竟有些差異,我本人搜尋文章時,也不喜歡搜出一堆其他語言的參考資料來,為了少製造些垃圾資訊積點德,就在標題前加了個C#。


訂閱-釋出模式又稱觀察者模式,很多資料只是介紹了個大致思想,很難找到把訂閱者和釋出者最大化解耦的參考程式碼。

我這裡貼個在我比較欣賞的一個設計上作了些許修改的版本:

using UnityEngine;
using System;
using System.Collections.Generic;

public class SubPubSystem 
{
    public Dictionary<string, Delegate> records = new Dictionary<string, Delegate>();

    public void Subscribe(string name, Action method) { _Subscribe(name, method); }
    public void Subscribe<T0>(string name, Action<T0> method) { _Subscribe(name, method); }
    public void Subscribe<T0, T1>(string name, Action<T0, T1> method) { _Subscribe(name, method); }
    public void Subscribe<T0, T1, T2>(string name, Action<T0, T1, T2> method) { _Subscribe(name, method); }
    public void Subscribe<T0, T1, T2, T3>(string name, Action<T0, T1, T2, T3> method) { _Subscribe(name, method); }

    public void UnSubscribe(string name, Action method) { _UnSubscribe(name, method); }
    public void UnSubscribe<T0>(string name, Action<T0> method) { _UnSubscribe(name, method); }
    public void UnSubscribe<T0, T1>(string name, Action<T0, T1> method) { _UnSubscribe(name, method); }
    public void UnSubscribe<T0, T1, T2>(string name, Action<T0, T1, T2> method) { _UnSubscribe(name, method); }
    public void UnSubscribe<T0, T1, T2, T3>(string name, Action<T0, T1, T2, T3> method) { _UnSubscribe(name, method); }

    public void _Subscribe(string name, Delegate method)
    {
        Delegate d;
        if (records.TryGetValue(name, out d))
        {
            d = Delegate.Combine(d, method);
            records[name] = d;
        }
        else
        {
            records.Add(name, method);
        }
    }

    public void _UnSubscribe(string name, Delegate method)
    {
        Delegate d;
        if (records.TryGetValue(name, out d))
        {
            d = Delegate.Remove(d, method);
            records[name] = d; 
        }
    }

    public void Publish(string name, params object[] args)
    {
        try
        {
            Delegate d;
            if (records.TryGetValue(name, out d))
            {
                if (d != null)
                {
                    d.DynamicInvoke(args);
                }             
            }
            else
            {
                records.Remove(name);
            }
        }
        catch(Exception ex)
        {
            Debug.LogError(ex.Message); 
        }
    }
}

使用也很簡單,在某個大系統模組裡new一個例項,在需要的地方呼叫其Subscribe、Publish方法就是了,假如遊戲規模小,甚至可以直接改成靜態類來用。

需要注意的點:

1.訂閱者方法若意外丟失了,比如Unity裡綁在某個物體上的指令碼隨著物體的銷燬也不見了,那釋出訊息呼叫時會引發異常。關於如何更好的探知呼叫的方法是否還存在的問題,幾個群問了下,沒得到很好的答覆,最好還是手工在OnDestroy裡UnSubscribe罷。若誰知道更好的方案,請賜教。

2.為簡單起見這裡最多隻支援四個引數,若需要更多引數,多複製貼上幾行就是了。


我個人還是很喜歡這個系統的簡單、靈活的。像.net framwork給出的EventHandler規範那種,還要自己繼承EventArgs寫許多訊息類,感覺略微有些麻煩。

暫時就在Besige中用這套系統來處理訊息事件了,以後遇到問題再完善。


最後說下下階段的計劃:

上文說到Besige還是留下大把的功能模組需要做,但我現在急切的想劍指另一塊垂涎已久的地盤——指令碼系統。

暫時把那些輕車熟路只是需要時間完成的工作放一放,先進入下一個相對陌生更有挑戰性的領域。

公司專案用的指令碼系統是自定義的一套DSL,虛擬機器也是自己寫的,感覺這樣完全沒必要,其技能指令碼甚至弱到連變數都沒法用。借用通用的指令碼語言和第三方庫明顯更實惠些,功能也更強大。

這樣具體來說無非就是Lua或Python了,Unity裡相對用Lua的明顯多一些,所以目標就鎖定在ulua_tolua上了,tolua的開發者同時提供了一個LuaFramework_NGUI/UGUI的框架,用來學習其架構也是不錯的。

我希望在消化其思想後,將ulua虛擬機器剝離出來,最小化的放進自己的專案,在此基礎上作一些指令碼程式設計的實踐,至少要做到用指令碼定義UI,然後視情況看能不能做點別的比如AI之類,甚至整個邏輯都拿lua來實現。

最後,搞定了這塊,很多小公司的面試者特別喜歡問的熱更新,自然也就搞定了。

最近工作也忙,整個週期可能會比較長,定在兩個月左右吧。

相關文章