Source path :Github-EventBus <https://github.com/yanshengjie/EventBus>
How much does event bus know (1) <http://www.cnblogs.com/sheng-jie/p/6970091.html>
How much does event bus know (2) <http://www.cnblogs.com/sheng-jie/p/7063011.html>

1. introduction


The concept of event bus may be strange to you , But when it comes to observers ( release - subscribe ) pattern , You may be familiar . Event bus is to publish - An implementation of subscription mode . It is a centralized event handling mechanism , Allow different components to communicate with each other without relying on each other , Achieve a decoupling purpose .

Let's take a look at the processing flow of the event bus :



Understand the basic concept and processing flow of event bus , Let's analyze how to implement event bus .

2. Return to nature

Before implementing event bus , We still have to trace the origin , Explore the essence of events and the implementation mechanism of publish and subscribe mode .

2.1. The nature of the event

Let's first explore the concept of events . They've read books , We should all remember the six elements of narrative : time , place , character , event ( cause , after , result ).

Let's take the case of registration , To explain .
User input user name , mailbox , After password , Click Register , After the input error free verification is passed , Register successfully and send email to user , Require users to activate mailbox validation .

There are two main events involved :

* Register events : The reason is that the user clicks the registration button , After input verification , The result is whether the registration is successful .
* Send mail event : The cause is that the user needs to verify the mailbox after successfully registering with the mailbox , By mail , The result is whether the message was sent successfully .

In fact, these six elements also apply to the handling process of events in our program . Developed WinForm The program knows , We're doing it UI When designing , Drag a registration button from the toolbox (btnRegister), Double click it ,VS It will automatically generate the following code for us :
void btnRegister_Click(object sender, EventArgs e) { // Event handling }
among object sender Refers to the object that issues the event , Here is button object ;EventArgs e Event parameters , It can be understood as a description of the event , They can be collectively referred to as event sources
. Code logic , It's the handling of events . We can call it event handling .

Said so much , It's nothing more than trying to see the essence through the phenomenon : Events are composed of event sources and event processing .

2.2. Publish and subscribe mode

Define a one to many dependency between objects , Make every time an object changes state , All objects that depend on it are notified and automatically updated . —— Publish and subscribe mode

There are two main roles of publish and subscribe mode :

* Issued by (Publisher): Also known as the observed , Notify all subscribers when status changes .
* Subscriber (Subscriber): Also known as observer , Subscribe to events and handle received events .
There are two ways to implement the publish and subscribe mode :

* Simple implementation : from Publisher Maintain a list of subscribers , Loop through the list to notify subscribers when the state changes .
* Implementation of delegation : from Publisher Define event delegation ,Subscriber Implement delegation .
in general , There are two keywords in publish and subscribe mode , Notifications and updates .
The observer shall be informed of the status change of the observed to make corresponding updates .
The solution is to inform other objects to make corresponding changes when the objects change .

If you draw a picture to show the process , The graph should be like this :



3 Implement publish and subscribe mode

Believe in the above explanation , Have a general impression of events and publish / subscribe mode . It is said that theory should be combined with practice , So it's better for us to tap the code with our fingers .
I will 『 Observer mode 』 Come fishing <http://www.jianshu.com/p/45675c73296d>
Based on this example , Improve a more general publish and subscribe mode by refactoring .
Code first :
/// <summary> /// Fish category enumeration /// </summary> public enum FishType { Crucian carp , carp , snakehead , Herring ,
Grass Carp , Perch }
The realization of fishing rod :
/// <summary> /// Fishing rod ( Observed ) /// </summary> public class FishingRod { public
delegate void FishingHandler(FishType type); // Declaration delegation public event FishingHandler
FishingEvent; // Claim event public void ThrowHook(FishingMan man) {
Console.WriteLine(" Start hooking !"); // Simulation of fish bite with random number , If the random number is even , Fish bite if (new Random().Next() %
2 == 0) { var type = (FishType) new Random().Next(0, 5);
Console.WriteLine(" small bell : Ding Ding Ding , Fish are biting "); if (FishingEvent != null)
FishingEvent(type); } } }
an angler :
/// <summary> /// an angler ( Observer ) /// </summary> public class FishingMan { public
FishingMan(string name) { Name = name; } public string Name { get; set; }
public int FishCount { get; set; } /// <summary> /// Fishermen should have fishing rods /// </summary>
public FishingRod FishingRod { get; set; } public void Fishing() {
this.FishingRod.ThrowHook(this); } public void Update(FishType type) {
FishCount++; Console.WriteLine("{0}: Catch one [{2}], Already caught {1} A fish !", Name, FishCount,
type); } }
The scene class is also very simple :
//1, Initialize fishing rod var fishingRod = new FishingRod(); //2, Statement Angler var jeff = new
FishingMan(" Sage "); //3. Distribution fishing rod jeff.FishingRod = fishingRod; //4, Registered observer
fishingRod.FishingEvent += jeff.Update; //5, Circular fishing while (jeff.FishCount < 5) {
jeff.Fishing(); Console.WriteLine("-------------------"); // sleep 5s
Thread.Sleep(5000); }

The code is simple , I'm sure you'll see . But obviously, this code implementation is only applicable to the current fishing scenario , If there are other scenarios that want to use this mode , We also need to redefine the delegation , Redefining event handling , Not very tired . in line with ”Don't
repeat yourself“ Principle of , We need to refactor it .

Combined with our discussion on the nature of events , Events are composed of event sources and event processing . For our case above ,public delegate void
FishingHandler(FishType type); This code shows the event source and event handling . The event source is FishType type, Event handling is naturally registered to
FishingHandler Delegation instance above .
Problem found , Obviously, our event source and event handling are not abstract enough , So it's not universal , Now let's do the transformation .

3.1. Extract event source

The event source should contain at least the time when the event occurred and the object that triggered it .
We extract IEventData Interface to encapsulate event sources :
/// <summary> /// Define event source interface , All event sources must implement this interface /// </summary> public interface
IEventData { /// <summary> /// Time of event /// </summary> DateTime EventTime { get;
set; } /// <summary> /// Object that triggered the event /// </summary> object EventSource { get; set;
} }
Naturally, we should give a default implementation EventData:
/// <summary> /// Event source : Describe event information , For parameter passing /// </summary> public class EventData :
IEventData { /// <summary> /// Time of event /// </summary> public DateTime EventTime
{ get; set; } /// <summary> /// Object that triggered the event /// </summary> public Object
EventSource { get; set; } public EventData() { EventTime = DateTime.Now; } }
in the light of Demo, The extended event sources are as follows :
public class FishingEventData : EventData { public FishType FishType { get;
set; } public FishingMan FisingMan { get; set; } }
After completion , We can put the FishingRod Declared delegate parameter type changed to FishingEventData Type , Namely public delegate void
FishingHandler(FishingEventData eventData); // Declaration delegation ;
Then modify FishingMan Of Update Method can be modified according to the parameter type defined by the delegation , I won't let go of the code , We'll do it ourselves .

At this point, we have unified the definition of event sources .

3.2. Extract event handler

Event sources are unified , The handling of that incident has to be limited . For example, if you name the event handling method at will , When registering an event, you need to match the parameter types defined by the delegation , How about trouble .

We take one IEventHandler Interface :
/// <summary> /// Define event handler common interface , All event handling must implement this interface /// </summary> public interface
IEventHandler { }
Event processing is bound to event source , So let's define a generic interface :
/// <summary> /// Generic event handler interface /// </summary> /// <typeparam
name="TEventData"></typeparam> public interface IEventHandler<TEventData> :
IEventHandler where TEventData : IEventData { /// <summary> /// The event handler implements this method to handle events
/// </summary> /// <param name="eventData"></param> void HandleEvent(TEventData
eventData); }
You might wonder , Why is an empty interface defined first ? Let's leave it to ourselves .

At this point, we have completed the abstraction of event processing . We will continue to transform our Demo. We let FishingMan realization IEventHandler Interface , Then modify the
fishingRod.FishingEvent += jeff.Update; Change to fishingRod.FishingEvent +=
jeff.HandleEvent; that will do . Code changes are simple , Also omitted here .

So you might think we've done the right thing Demo Transformation of . But in fact , We need to find out one more question —— If this FishingMan There are other events subscribed to , What should we do ?
Smart as you , You immediately thought that event sources could be used to distinguish and process .
public class FishingMan : IEventHandler<IEventData> { // Omit other codes public void
HandleEvent(IEventData eventData) { if (eventData is FishingEventData) { //do
something } if(eventData is XxxEventData) { //do something else } } }
thus , This mode has been implemented to this point and can be used in general .

4. Implement event bus

General publish and subscribe mode is not our purpose , Our goal is a centralized event handling mechanism , And each module has no dependence on each other . How can we do that ? In the same way, we will analyze and transform step by step .

4.1. Analyze problems

Think about it , Every time in order to implement this pattern , All of the following three steps should be completed :

* Event publisher defines event delegation
* Event subscriber defines event processing logic
* Subscription events displayed
Although there are only three steps , But these three steps are very complicated . There are also dependencies between event publishers and event subscribers ( It is reflected in the registration and logout of events to be displayed by subscribers ). And when there are too many events , Implemented directly in subscribers
IEventHandler Interface handling multiple event logic is obviously not appropriate , Principle of single responsibility in violation of law . Three problems are exposed here :

* How to streamline steps ?
* How to release the dependence between the publisher and the subscriber ?
* How to avoid handling multiple event logic at the same time in subscribers ?
Thinking with questions , We will be closer to the truth .

Want to streamline steps , So we need to look for commonalities . Commonness is the essence of events , That is, we extract two interfaces for event source and event processing .

Want to break dependency , Add a mediation between the publisher and the subscriber .

Want to avoid subscribers handling too much event logic at the same time , Then we extract the processing of event logic to the outside of the subscriber .

We have ideas , Let's implement it .

4.2. solve the problem

Based on the idea of "easy before hard" , Let's solve the above problems .

4.2.1. realization IEventHandler

Let's solve the third problem above : How to avoid handling multiple event logic at the same time in subscribers ?

Nature is aimed at different event sources IEventData Achieve different IEventHandler. The processing logic of the modified fishing event is as follows :
/// <summary> /// Fishing incident handling /// </summary> public class FishingEventHandler :
IEventHandler<FishingEventData> { public void HandleEvent(FishingEventData
eventData) { eventData.FishingMan.FishCount++;
Console.WriteLine("{0}: Catch one [{2}], Already caught {1} A fish !", eventData.FishingMan.Name,
eventData.FishingMan.FishCount, eventData.FishType); } }
Then we can remove the FishingMan Implemented in IEventHandler Interface .
Then change the event registration to fishingRod.FishingEvent += new FishingEventHandler().HandleEvent; that will do .

4.2.2. Unified registration event

Solution of the previous problem , Help us solve the first problem : How to streamline processes ?
Why? , Because we define the corresponding event processing according to the event source . That is to say, events can be distinguished according to their sources .
so what ? reflex , We can register events uniformly through reflection .
stay FishingRod Using reflection in the constructor of , Unified registration realized IEventHandler<FishingEventData> Instance method of type HandleEvent

public FishingRod() { Assembly assembly = Assembly.GetExecutingAssembly();
foreach (var type in assembly.GetTypes()) { if
(typeof(IEventHandler).IsAssignableFrom(type))// Judge whether the current type is implemented IEventHandler Interface {
Type handlerInterface = type.GetInterface("IEventHandler`1");// Get the generic interface implemented by this class Type
eventDataType = handlerInterface.GetGenericArguments()[0]; // Gets the parameter type specified by the generic interface
// If the parameter type is FishingEventData, Event source matching if
(eventDataType.Equals(typeof(FishingEventData))) { // Create instance var handler =
Activator.CreateInstance(type) as IEventHandler<FishingEventData>; // Register events
FishingEvent += handler.HandleEvent; } } } }
such , We can remove the display registration code from the scene class fishingRod.FishingEvent += new
FishingEventHandler().HandleEvent;.

4.2.3. De dependence

How to relieve dependence ? In fact, the answer lies in the two figures in this paper , By careful comparison, we can see that ,Event
Bus It's equivalent to a Publisher and Subscriber Bridge in the middle . It's isolated Publlisher and Subscriber Direct dependence between , Takes over the publish and subscribe logic for all events , And be responsible for the transfer of events .

Event Bus It's finally coming to the stage !!!

Analyze it , If EventBus To take over publishing and subscriptions for all events , Then it needs to have a container to record the event source and event processing . So how to trigger ? With event sources , We can naturally find the binding event processing logic , Triggered by reflection . The code is as follows :
/// <summary> /// Event bus /// </summary> public class EventBus { public static
EventBus Default => new EventBus(); /// <summary> /// Defining thread safe collections /// </summary>
private readonly ConcurrentDictionary<Type, List<Type>>
_eventAndHandlerMapping; public EventBus() { _eventAndHandlerMapping = new
ConcurrentDictionary<Type, List<Type>>(); MapEventToHandler(); } /// <summary>
/// Through reflection , Binding event source to event processing /// </summary> private void MapEventToHandler() { Assembly
assembly = Assembly.GetEntryAssembly(); foreach (var type in
assembly.GetTypes()) { if
(typeof(IEventHandler).IsAssignableFrom(type))// Judge whether the current type is implemented IEventHandler Interface {
Type handlerInterface = type.GetInterface("IEventHandler`1");// Get the generic interface implemented by this class if
(handlerInterface != null) { Type eventDataType =
handlerInterface.GetGenericArguments()[0]; // Gets the parameter type specified by the generic interface 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> /// Manually binding event source and event handling /// </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>
/// Unbind event source and event processing manually /// </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>
/// Trigger the bound event processing according to the event source /// </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 }); } } } } }
Event bus mainly defines three methods , register , Unregister , Event triggered . Another point is that we bind event source and event processing through reflection in the constructor .
Code comments are clear , There's no more explanation here .

Let's modify it Demo, modify FishingRod Event triggered by :
/// <summary> /// Lower hook /// </summary> public void ThrowHook(FishingMan man) {
Console.WriteLine(" Start hooking !"); // Simulation of fish bite with random number , If the random number is even , Fish bite if (new Random().Next() %
2 == 0) { var a = new Random(10).Next(); var type = (FishType)new
Random().Next(0, 5); Console.WriteLine(" small bell : Ding Ding Ding , Fish are biting "); if (FishingEvent !=
null) { var eventData = new FishingEventData() { FishType = type, FishingMan =
man }; //FishingEvent(eventData);// No longer need to trigger through event delegation
EventBus.Default.Trigger<FishingEventData>(eventData);// It can be triggered directly through the event bus } } }
thus , The prototype of event bus has been formed !

5. Summary of event bus

Through the above step-by-step analysis and Practice , Discovering event bus is not an advanced concept , As long as we are good at thinking , Hands on , It can also realize its own event bus .
According to our implementation , Here are a few :

* Event bus maintains a mapping Dictionary of event source and event processing ;
* Through singleton mode , Ensure the only entry to the event bus ;
* Using reflection to complete the initialization binding of event source and event processing ;
* Provide unified event registration , Unregister and trigger interface .
last , The implementation of the above event bus is just a prototype , There are many potential problems . Yes 兴趣的不妨思考完善一下,我也会继续更新完善,尽情期待!

参考资料

ABP EventBus
<https://github.com/aspnetboilerplate/aspnetboilerplate/tree/dev/src/Abp/Events/Bus>
DDD~领域事件与事件总线 <http://www.cnblogs.com/lori/p/3476703.html>
DDD事件总线的实现 <http://www.cnblogs.com/dehai/p/4887998.html>