事件總線知多少(1)

源碼路徑:Github-EventBus
事件總線知多少(1)
事件總線知多少(2)

1. 引言

事件總線這個(gè)概念對(duì)你來(lái)說(shuō)可能很陌生,但提到觀察者(發(fā)布-訂閱)模式,你也許就很熟悉。事件總線是對(duì)發(fā)布-訂閱模式的一種實(shí)現(xiàn)霎挟。它是一種集中式事件處理機(jī)制夷陋,允許不同的組件之間進(jìn)行彼此通信而又不需要相互依賴,達(dá)到一種解耦的目的先紫。

我們來(lái)看看事件總線的處理流程:

圖1:EventBus流程

了解了事件總線的基本概念和處理流程,下面我們就來(lái)分析下如何去實(shí)現(xiàn)事件總線筹煮。

2.回歸本質(zhì)

在動(dòng)手實(shí)現(xiàn)事件總線之前遮精,我們還是要追本溯源,探索一下事件的本質(zhì)和發(fā)布訂閱模式的實(shí)現(xiàn)機(jī)制败潦。

2.1.事件的本質(zhì)

我們先來(lái)探討一下事件的概念本冲。都是讀過(guò)書(shū)的,應(yīng)該都還記得記敘文的六要素:時(shí)間劫扒、地點(diǎn)檬洞、人物、事件(起因粟关、經(jīng)過(guò)疮胖、結(jié)果)。

我們拿注冊(cè)的案例闷板,來(lái)解釋一下澎灸。
用戶輸入用戶名、郵箱遮晚、密碼后性昭,點(diǎn)擊注冊(cè),輸入無(wú)誤校驗(yàn)通過(guò)后县遣,注冊(cè)成功并發(fā)送郵件給用戶糜颠,要求用戶進(jìn)行郵箱驗(yàn)證激活。

這里面就涉及了兩個(gè)主要事件:

  1. 注冊(cè)事件:起因是用戶點(diǎn)擊了注冊(cè)按鈕萧求,經(jīng)過(guò)是輸入校驗(yàn)其兴,結(jié)果是是否注冊(cè)成功。
  2. 發(fā)送郵件事件:起因是用戶使用郵箱注冊(cè)成功需要驗(yàn)證郵箱夸政,經(jīng)過(guò)是郵件發(fā)送元旬,結(jié)果是郵件是否發(fā)送成功。

其實(shí)這六要素也適用于我們程序中事件的處理過(guò)程。開(kāi)發(fā)過(guò)WinForm程序的都知道匀归,我們?cè)谧鯱I設(shè)計(jì)的時(shí)候坑资,從工具箱拖入一個(gè)注冊(cè)按鈕(btnRegister),雙擊它穆端,VS就會(huì)自動(dòng)幫我們生成如下代碼:

void btnRegister_Click(object sender, EventArgs e)
{
 // 事件的處理
}

其中object sender指代發(fā)出事件的對(duì)象袱贮,這里也就是button對(duì)象;EventArgs e 事件參數(shù)体啰,可以理解為對(duì)事件的描述 攒巍,它們可以統(tǒng)稱為事件源。其中的代碼邏輯狡赐,就是對(duì)事件的處理窑业。我們可以統(tǒng)稱為事件處理钦幔。

說(shuō)了這么多枕屉,無(wú)非是想透過(guò)現(xiàn)象看本質(zhì):事件是由事件源觸發(fā)并由事件處理消費(fèi)(An event is raised by an event source and consumed by an event handler)。

2.2. 發(fā)布訂閱模式

定義對(duì)象間一種一對(duì)多的依賴關(guān)系鲤氢,使得每當(dāng)一個(gè)對(duì)象改變狀態(tài)搀擂,則所有依賴于它的對(duì)象都會(huì)得到通知并被自動(dòng)更新。 ——發(fā)布訂閱模式

發(fā)布訂閱模式主要有兩個(gè)角色:

  • 發(fā)布方(Publisher):也稱為被觀察者卷玉,當(dāng)狀態(tài)改變時(shí)負(fù)責(zé)通知所有訂閱者哨颂。
  • 訂閱方(Subscriber):也稱為觀察者,訂閱事件并對(duì)接收到的事件進(jìn)行處理相种。

發(fā)布訂閱模式有兩種實(shí)現(xiàn)方式:

  • 簡(jiǎn)單的實(shí)現(xiàn)方式:由Publisher維護(hù)一個(gè)訂閱者列表威恼,當(dāng)狀態(tài)改變時(shí)循環(huán)遍歷列表通知訂閱者。
  • 委托的實(shí)現(xiàn)方式:由Publisher定義事件委托寝并,Subscriber實(shí)現(xiàn)委托箫措。

總的來(lái)說(shuō),發(fā)布訂閱模式中有兩個(gè)關(guān)鍵字衬潦,通知和更新斤蔓。
被觀察者狀態(tài)改變通知觀察者做出相應(yīng)更新。
解決的是當(dāng)對(duì)象改變時(shí)需要通知其他對(duì)象做出相應(yīng)改變的問(wèn)題镀岛。

如果畫(huà)一個(gè)圖來(lái)表示這個(gè)流程的畫(huà)弦牡,圖形應(yīng)該是這樣的:

圖2:發(fā)布訂閱模式流程

3 實(shí)現(xiàn)發(fā)布訂閱模式

相信通過(guò)上面的解釋,對(duì)事件和發(fā)布訂閱模式有了一個(gè)大概的印象漂羊。都說(shuō)理論要與實(shí)踐相結(jié)合驾锰,所以我們還是動(dòng)動(dòng)手指敲敲代碼比較好。
我將以『觀察者模式』來(lái)釣魚(yú)這個(gè)例子為基礎(chǔ)走越,通過(guò)重構(gòu)的方式來(lái)完善一個(gè)更加通用的發(fā)布訂閱模式椭豫。
先上代碼:

/// <summary>
/// 魚(yú)的品類枚舉
/// </summary>
public enum FishType
{
    鯽魚(yú),
    鯉魚(yú),
    黑魚(yú),
    青魚(yú),
    草魚(yú),
    鱸魚(yú)
}

釣魚(yú)竿的實(shí)現(xiàn):

 /// <summary>
 ///     魚(yú)竿(被觀察者)
 /// </summary>
 public class FishingRod
 {
     public delegate void FishingHandler(FishType type); //聲明委托
     public event FishingHandler FishingEvent; //聲明事件

     public void ThrowHook(FishingMan man)
     {
         Console.WriteLine("開(kāi)始下鉤!");

         //用隨機(jī)數(shù)模擬魚(yú)咬鉤,若隨機(jī)數(shù)為偶數(shù)捻悯,則為魚(yú)咬鉤
         if (new Random().Next() % 2 == 0)
         {
             var type = (FishType) new Random().Next(0, 5);
             Console.WriteLine("鈴鐺:叮叮叮匆赃,魚(yú)兒咬鉤了");
             if (FishingEvent != null)
                 FishingEvent(type);
         }
     }
 }

垂釣者:

/// <summary>
///     垂釣者(觀察者)
/// </summary>
public class FishingMan
{
    public FishingMan(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    public int FishCount { get; set; }

    /// <summary>
    /// 垂釣者自然要有魚(yú)竿啊
    /// </summary>
    public FishingRod FishingRod { get; set; }

    public void Fishing()
    {
        this.FishingRod.ThrowHook(this);
    }

    public void Update(FishType type)
    {
        FishCount++;
        Console.WriteLine("{0}:釣到一條[{2}],已經(jīng)釣到{1}條魚(yú)了今缚!", Name, FishCount, type);
    }
}

場(chǎng)景類也很簡(jiǎn)單:

//1算柳、初始化魚(yú)竿
var fishingRod = new FishingRod();

//2、聲明垂釣者
var jeff = new FishingMan("圣杰");

//3.分配魚(yú)竿
jeff.FishingRod = fishingRod;

//4姓言、注冊(cè)觀察者
fishingRod.FishingEvent += jeff.Update;

//5瞬项、循環(huán)釣魚(yú)
while (jeff.FishCount < 5)
{
    jeff.Fishing();
    Console.WriteLine("-------------------");
    //睡眠5s
    Thread.Sleep(5000);
}

代碼很簡(jiǎn)單,相信你一看就明白何荚。但很顯然這個(gè)代碼實(shí)現(xiàn)僅適用于當(dāng)前這個(gè)釣魚(yú)場(chǎng)景囱淋,假如有其他場(chǎng)景也想使用這個(gè)模式,我們還需要重新定義委托餐塘,重新定義事件處理妥衣,豈不很累。本著”Don't repeat yourself“的原則戒傻,我們要對(duì)其進(jìn)行重構(gòu)税手。

結(jié)合我們對(duì)事件本質(zhì)的探討,事件是由事件源和事件處理組成需纳。針對(duì)我們上面的案例來(lái)說(shuō)芦倒,public delegate void FishingHandler(FishType type);這句代碼就已經(jīng)說(shuō)明了事件源和事件處理。事件源就是FishType type不翩,事件處理自然是注冊(cè)到FishingHandler上面的委托實(shí)例兵扬。
問(wèn)題找到了,很顯然是我們的事件源和事件處理不夠抽象口蝠,所以不能通用器钟,下面咱們就來(lái)動(dòng)手改造。

3.1. 提取事件源

事件源應(yīng)該至少包含事件發(fā)生的時(shí)間和觸發(fā)事件的對(duì)象亚皂。
我們提取IEventData接口來(lái)封裝事件源:

/// <summary>
/// 定義事件源接口俱箱,所有的事件源都要實(shí)現(xiàn)該接口
/// </summary>
public interface IEventData
{
    /// <summary>
    /// 事件發(fā)生的時(shí)間
    /// </summary>
    DateTime EventTime { get; set; }

    /// <summary>
    /// 觸發(fā)事件的對(duì)象
    /// </summary>
    object EventSource { get; set; }
}

自然我們應(yīng)該給一個(gè)默認(rèn)的實(shí)現(xiàn)EventData

/// <summary>
/// 事件源:描述事件信息,用于參數(shù)傳遞
/// </summary>
public class EventData : IEventData
{
    /// <summary>
    /// 事件發(fā)生的時(shí)間
    /// </summary>
    public DateTime EventTime { get; set; }

    /// <summary>
    /// 觸發(fā)事件的對(duì)象
    /// </summary>
    public Object EventSource { get; set; }

    public EventData()
    {
        EventTime = DateTime.Now;
    }
}

針對(duì)Demo灭必,擴(kuò)展事件源如下:

public class FishingEventData : EventData
{
    public FishType FishType { get; set; }
    public FishingMan FisingMan { get; set; }
}

完成后狞谱,我們就可以去把在FishingRod聲明的委托參數(shù)類型改為FishingEventData類型了,即public delegate void FishingHandler(FishingEventData eventData); //聲明委托禁漓;
然后修改FishingManUpdate方法按委托定義的參數(shù)類型修改即可跟衅,代碼我就不放了,大家自行腦補(bǔ)播歼。

到這一步我們就統(tǒng)一了事件源的定義方式伶跷。

3.2.提取事件處理器

事件源統(tǒng)一了掰读,那事件處理也得加以限制。比如如果隨意命名事件處理方法名叭莫,那在進(jìn)行事件注冊(cè)的時(shí)候還要去按照委托定義的參數(shù)類型去匹配蹈集,豈不麻煩。

我們提取一個(gè)IEventHandler接口:

 /// <summary>
 /// 定義事件處理器公共接口雇初,所有的事件處理都要實(shí)現(xiàn)該接口
 /// </summary>
 public interface IEventHandler
 {
 }

事件處理要與事件源進(jìn)行綁定拢肆,所以我們?cè)賮?lái)定義一個(gè)泛型接口:

 /// <summary>
 /// 泛型事件處理器接口
 /// </summary>
 /// <typeparam name="TEventData"></typeparam>
 public interface IEventHandler<TEventData> : IEventHandler where TEventData : IEventData
 {
     /// <summary>
     /// 事件處理器實(shí)現(xiàn)該方法來(lái)處理事件
     /// </summary>
     /// <param name="eventData"></param>
     void HandleEvent(TEventData eventData);
 }

你可能會(huì)納悶,為什么先定義了一個(gè)空接口靖诗?這里就留給自己思考吧郭怪。

至此我們就完成了事件處理的抽象。我們?cè)倮^續(xù)去改造我們的Demo刊橘。我們讓FishingMan實(shí)現(xiàn)IEventHandler接口鄙才,然后修改場(chǎng)景類中將fishingRod.FishingEvent += jeff.Update;改為fishingRod.FishingEvent += jeff.HandleEvent;即可。代碼改動(dòng)很簡(jiǎn)單促绵,同樣在此略去攒庵。

至此你可能覺(jué)得我們完成了對(duì)Demo的改造。但事實(shí)上呢绞愚,我們還要弄清一個(gè)問(wèn)題——如果這個(gè)FishingMan訂閱的有其他的事件叙甸,我們?cè)撊绾翁幚恚?br> 聰穎如你,你立馬想到了可以通過(guò)事件源來(lái)進(jìn)行區(qū)分處理位衩。

public class FishingMan : IEventHandler<IEventData>
{
    //省略其他代碼
    public void HandleEvent(IEventData eventData)
    {
        if (eventData is FishingEventData)
        {
            //do something
        }

        if(eventData is XxxEventData)
        {
            //do something else
        }
    }
}

至此,這個(gè)模式實(shí)現(xiàn)到這個(gè)地步基本已經(jīng)可以通用了熔萧。

4. 實(shí)現(xiàn)事件總線

通用的發(fā)布訂閱模式不是我們的目的糖驴,我們的目的是一個(gè)集中式的事件處理機(jī)制,且各個(gè)模塊之間相互不產(chǎn)生依賴佛致。那我們?nèi)绾巫龅侥刂疲客瑯游覀冞€是一步一步的進(jìn)行分析改造。

4.1.分析問(wèn)題

思考一下俺榆,每次為了實(shí)現(xiàn)這個(gè)模式感昼,都要完成以下三步:

  1. 事件發(fā)布方定義事件委托
  2. 事件訂閱方定義事件處理邏輯
  3. 顯示的訂閱事件

雖然只有三步,但這三步已經(jīng)很繁瑣了罐脊。而且事件發(fā)布方和事件訂閱方還存在著依賴(體現(xiàn)在訂閱者要顯示的進(jìn)行事件的注冊(cè)和注銷上)定嗓。而且當(dāng)事件過(guò)多時(shí),直接在訂閱者中實(shí)現(xiàn)IEventHandler接口處理多個(gè)事件邏輯顯然不太合適萍桌,違法單一職責(zé)原則宵溅。這里就暴露了三個(gè)問(wèn)題:

  1. 如何精簡(jiǎn)步驟?
  2. 如何解除發(fā)布方與訂閱方的依賴上炎?
  3. 如何避免在訂閱者中同時(shí)處理多個(gè)事件邏輯恃逻?

帶著問(wèn)題思考,我們就會(huì)更接近真相。

想要精簡(jiǎn)步驟寇损,那我們需要尋找共性凸郑。共性就是事件的本質(zhì),也就是我們針對(duì)事件源和事件處理提取出來(lái)的兩個(gè)接口矛市。

想要解除依賴线椰,那就要在發(fā)布方和訂閱方之間添加一個(gè)中介。

想要避免訂閱者同時(shí)處理過(guò)多事件邏輯尘盼,那我們就把事件邏輯的處理提取到訂閱者外部憨愉。

思路有了,下面我們就來(lái)實(shí)施吧卿捎。

4.2.解決問(wèn)題

本著先易后難的思想配紫,我們下面就來(lái)解決以上問(wèn)題。

4.2.1. 實(shí)現(xiàn)IEventHandler

我們先解決上面的第三個(gè)問(wèn)題:如何避免在訂閱者中同時(shí)處理多個(gè)事件邏輯午阵?

自然是針對(duì)不同的事件源IEventData實(shí)現(xiàn)不同的IEventHandler躺孝。改造后的釣魚(yú)事件處理邏輯如下:

/// <summary>
/// 釣魚(yú)事件處理
/// </summary>
public class FishingEventHandler : IEventHandler<FishingEventData>
{
    public void HandleEvent(FishingEventData eventData)
    {
        eventData.FishingMan.FishCount++;

        Console.WriteLine("{0}:釣到一條[{2}],已經(jīng)釣到{1}條魚(yú)了底桂!",
            eventData.FishingMan.Name, eventData.FishingMan.FishCount, eventData.FishType);

    }
}

這時(shí)我們就可以移除在FishingMan中實(shí)現(xiàn)的IEventHandler接口了植袍。
然后將事件注冊(cè)改為fishingRod.FishingEvent += new FishingEventHandler().HandleEvent;即可。

4.2.2. 統(tǒng)一注冊(cè)事件

上一個(gè)問(wèn)題的解決籽懦,有助于我們解決第一個(gè)問(wèn)題:如何精簡(jiǎn)流程于个?
為什么呢,因?yàn)槲覀兪歉鶕?jù)事件源定義相應(yīng)的事件處理的暮顺。也就是我們之前說(shuō)的可以根據(jù)事件源來(lái)區(qū)分事件厅篓。
然后呢?反射捶码,我們可以通過(guò)反射來(lái)進(jìn)行事件的統(tǒng)一注冊(cè)羽氮。
FishingRod的構(gòu)造函數(shù)中使用反射,統(tǒng)一注冊(cè)實(shí)現(xiàn)了IEventHandler<FishingEventData>類型的實(shí)例方法HandleEvent

public FishingRod()
{
    Assembly assembly = Assembly.GetExecutingAssembly();

    foreach (var type in assembly.GetTypes())
    {
        if (typeof(IEventHandler).IsAssignableFrom(type))//判斷當(dāng)前類型是否實(shí)現(xiàn)了IEventHandler接口
        {
            Type handlerInterface = type.GetInterface("IEventHandler`1");//獲取該類實(shí)現(xiàn)的泛型接口
            Type eventDataType = handlerInterface.GetGenericArguments()[0]; // 獲取泛型接口指定的參數(shù)類型

            //如果參數(shù)類型是FishingEventData惫恼,則說(shuō)明事件源匹配
            if (eventDataType.Equals(typeof(FishingEventData)))
            {
                //創(chuàng)建實(shí)例
                var handler = Activator.CreateInstance(type) as IEventHandler<FishingEventData>;
                //注冊(cè)事件
                FishingEvent += handler.HandleEvent;
            }
        }
    }
}

這樣档押,我們就可以移出場(chǎng)景類中的顯示注冊(cè)代碼fishingRod.FishingEvent += new FishingEventHandler().HandleEvent;

4.2.3. 解除依賴

如何解除依賴呢祈纯?其實(shí)答案就在本文的兩張圖上令宿,仔細(xì)對(duì)比我們可以很直觀的看到,Event Bus就相當(dāng)于一個(gè)介于Publisher和Subscriber中間的橋梁盆繁。它隔離了Publlisher和Subscriber之間的直接依賴掀淘,接管了所有事件的發(fā)布和訂閱邏輯,并負(fù)責(zé)事件的中轉(zhuǎn)油昂。

Event Bus終于要粉墨登場(chǎng)了8锫ΑG惴 !
分析一下拦惋,如果EventBus要接管所有事件的發(fā)布和訂閱匆浙,那它則需要有一個(gè)容器來(lái)記錄事件源和事件處理。那又如何觸發(fā)呢厕妖?有了事件源首尼,我們就自然能找到綁定的事件處理邏輯,通過(guò)反射觸發(fā)言秸。代碼如下:

/// <summary>
/// 事件總線
/// </summary>
public class EventBus
{
    public static EventBus Default => new EventBus();

    /// <summary>
    /// 定義線程安全集合
    /// </summary>
    private readonly ConcurrentDictionary<Type, List<Type>> _eventAndHandlerMapping;

    public EventBus()
    {
        _eventAndHandlerMapping = new ConcurrentDictionary<Type, List<Type>>();
        MapEventToHandler();
    }

    /// <summary>
    ///通過(guò)反射软能,將事件源與事件處理綁定
    /// </summary>
    private void MapEventToHandler()
    {
        Assembly assembly = Assembly.GetEntryAssembly();
        foreach (var type in assembly.GetTypes())
        {
            if (typeof(IEventHandler).IsAssignableFrom(type))//判斷當(dāng)前類型是否實(shí)現(xiàn)了IEventHandler接口
            {
                Type handlerInterface = type.GetInterface("IEventHandler`1");//獲取該類實(shí)現(xiàn)的泛型接口
                if (handlerInterface != null)
                {
                    Type eventDataType = handlerInterface.GetGenericArguments()[0]; // 獲取泛型接口指定的參數(shù)類型

                    if (_eventAndHandlerMapping.ContainsKey(eventDataType))
                    {
                        List<Type> handlerTypes = _eventAndHandlerMapping[eventDataType];
                        handlerTypes.Add(type);
                        _eventAndHandlerMapping[eventDataType] = handlerTypes;
                    }
                    else
                    {
                        var handlerTypes = new List<Type> { type };
                        _eventAndHandlerMapping[eventDataType] = handlerTypes;
                    }
                }
            }
        }
    }

    /// <summary>
    /// 手動(dòng)綁定事件源與事件處理
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="eventHandler"></param>
    public void Register<TEventData>(Type eventHandler)
    {
        List<Type> handlerTypes = _eventAndHandlerMapping[typeof(TEventData)];
        if (!handlerTypes.Contains(eventHandler))
        {
            handlerTypes.Add(eventHandler);
            _eventAndHandlerMapping[typeof(TEventData)] = handlerTypes;
        }
    }

    /// <summary>
    /// 手動(dòng)解除事件源與事件處理的綁定
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="eventHandler"></param>
    public void UnRegister<TEventData>(Type eventHandler)
    {
        List<Type> handlerTypes = _eventAndHandlerMapping[typeof(TEventData)];
        if (handlerTypes.Contains(eventHandler))
        {
            handlerTypes.Remove(eventHandler);
            _eventAndHandlerMapping[typeof(TEventData)] = handlerTypes;
        }
    }

    /// <summary>
    /// 根據(jù)事件源觸發(fā)綁定的事件處理
    /// </summary>
    /// <typeparam name="TEventData"></typeparam>
    /// <param name="eventData"></param>
    public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
    {
        List<Type> handlers = _eventAndHandlerMapping[eventData.GetType()];

        if (handlers != null && handlers.Count > 0)
        {
            foreach (var handler in handlers)
            {
                MethodInfo methodInfo = handler.GetMethod("HandleEvent");
                if (methodInfo != null)
                {
                    object obj = Activator.CreateInstance(handler);
                    methodInfo.Invoke(obj, new object[] { eventData });
                }
            }
        }
    }
}

事件總線主要定義三個(gè)方法,注冊(cè)举畸、取消注冊(cè)查排、事件觸發(fā)。還有一點(diǎn)就是我們?cè)跇?gòu)造函數(shù)中通過(guò)反射去進(jìn)行事件源和事件處理的綁定抄沮。
代碼注釋已經(jīng)很清楚了跋核,這里就不過(guò)多解釋了。

下面我們就來(lái)修改Demo叛买,修改FishingRod的事件觸發(fā):

/// <summary>
/// 下鉤
/// </summary>
public void ThrowHook(FishingMan man)
{
    Console.WriteLine("開(kāi)始下鉤砂代!");

    //用隨機(jī)數(shù)模擬魚(yú)咬鉤,若隨機(jī)數(shù)為偶數(shù)率挣,則為魚(yú)咬鉤
    if (new Random().Next() % 2 == 0)
    {
        var a = new Random(10).Next();
        var type = (FishType)new Random().Next(0, 5);
        Console.WriteLine("鈴鐺:叮叮叮刻伊,魚(yú)兒咬鉤了");
        if (FishingEvent != null)
        {
            var eventData = new FishingEventData() { FishType = type, FishingMan = man };
            //FishingEvent(eventData);//不再需要通過(guò)事件委托觸發(fā)
            EventBus.Default.Trigger<FishingEventData>(eventData);//直接通過(guò)事件總線觸發(fā)即可
        }
    }
}

至此,事件總線的雛形已經(jīng)形成难礼!

5.事件總線的總結(jié)

通過(guò)上面一步一步的分析和實(shí)踐娃圆,發(fā)現(xiàn)事件總線也不是什么高深的概念,只要我們自己善于思考蛾茉,勤于動(dòng)手,也能實(shí)現(xiàn)自己的事件總線撩鹿。
根據(jù)我們的實(shí)現(xiàn)谦炬,大概總結(jié)出以下幾條:

  1. 事件總線維護(hù)一個(gè)事件源與事件處理的映射字典;
  2. 通過(guò)單例模式节沦,確保事件總線的唯一入口键思;
  3. 利用反射完成事件源與事件處理的初始化綁定;
  4. 提供統(tǒng)一的事件注冊(cè)甫贯、取消注冊(cè)和觸發(fā)接口吼鳞。

最后,以上事件總線的實(shí)現(xiàn)只是一個(gè)雛形叫搁,還有很多潛在的問(wèn)題赔桌。有興趣的不妨思考完善一下供炎,我也會(huì)繼續(xù)更新,盡情期待疾党。


參考資料

ABP EventBus
DDD~領(lǐng)域事件與事件總線
DDD事件總線的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末音诫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雪位,更是在濱河造成了極大的恐慌竭钝,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雹洗,死亡現(xiàn)場(chǎng)離奇詭異香罐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)时肿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門庇茫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人嗜侮,你說(shuō)我怎么就攤上這事港令。” “怎么了锈颗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵顷霹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我击吱,道長(zhǎng)淋淀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任覆醇,我火速辦了婚禮朵纷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘永脓。我一直安慰自己袍辞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布常摧。 她就那樣靜靜地躺著搅吁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪落午。 梳的紋絲不亂的頭發(fā)上谎懦,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音溃斋,去河邊找鬼界拦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梗劫,可吹牛的內(nèi)容都是我干的享甸。 我是一名探鬼主播截碴,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼枪萄!你這毒婦竟也來(lái)了隐岛?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓷翻,失蹤者是張志新(化名)和其女友劉穎聚凹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體齐帚,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妒牙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了对妄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湘今。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖剪菱,靈堂內(nèi)的尸體忽然破棺而出摩瞎,到底是詐尸還是另有隱情,我是刑警寧澤孝常,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布旗们,位于F島的核電站,受9級(jí)特大地震影響构灸,放射性物質(zhì)發(fā)生泄漏上渴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一喜颁、第九天 我趴在偏房一處隱蔽的房頂上張望稠氮。 院中可真熱鬧,春花似錦半开、人聲如沸隔披。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锹锰。三九已至,卻和暖如春漓库,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背园蝠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工渺蒿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彪薛。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓茂装,卻偏偏與公主長(zhǎng)得像怠蹂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子少态,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容