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. Returning to nature

Before implementing event bus, We still have to trace the origin, Explore the essence of events and the implementation mechanism of publish 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 registration, After the input error free verification is passed, Register successfully and send email to user, Require user to activate mailbox authentication.

There are two main events involved:

* Registration event: 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 reason is that the user uses the mailbox to register successfully and needs to verify 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. DevelopedWinForm The program knows, We are doingUI 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 }
amongobject sender Refers to the object that issues the event, Here isbutton 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:

* Publisher(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: fromPublisher Maintain a list of subscribers, Loop through the list to notify subscribers when the state changes.
* Implementation of delegation: fromPublisher Define event delegation,Subscriber Implementation of 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.
First up code:
/// <summary> /// Fish category enumeration /// </summary> public enum FishType { Crucian carp, carp, Snakehead, Black carp,
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; // Declaration event public void ThrowHook(FishingMan man) {
Console.WriteLine(" Start the next hook.!"); // 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: Tinkling, 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(" Saint"); //3. Distribution of fishing rods jeff.FishingRod = fishingRod; //4, Registered observer
fishingRod.FishingEvent += jeff.Update; //5, Cycle fishing while (jeff.FishCount < 5) {
jeff.Fishing(); Console.WriteLine("-------------------"); // sleep5s
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, Is it not very tired?. In line with”Don't
repeat yourself“ Principles, 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 isFishType 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 extractIEventData 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 implementationEventData:
/// <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 ofDemo, 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 theFishingRod Declared delegate parameter type changed toFishingEventData Type, Namelypublic delegate void
FishingHandler(FishingEventData eventData); // Declaration delegation;
Then modifyFishingMan OfUpdate 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, No trouble..

We take oneIEventHandler 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 ourDemo. We letFishingMan RealizationIEventHandler Interface, Then modify the
fishingRod.FishingEvent += jeff.Update; Change tofishingRod.FishingEvent +=
jeff.HandleEvent; that will do. Code changes are simple, Also omitted here.

So you might think we've done the right thingDemo Transformation. But in fact, We need to find out one more question—— If thisFishingMan There are other events subscribed to, What should we do?
Clever as you, You immediately thought that event sources could be used to distinguish processing.
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 pattern 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. Analysis problem

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.

There is a train of thought. 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. RealizationIEventHandler

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 sourcesIEventData Achieve differentIEventHandler. 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 theFishingMan Implemented inIEventHandler Interface.
Then change the event registration tofishingRod.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.
stayFishingRod Using reflection in the constructor of, Unified registration realizedIEventHandler<FishingEventData> Instance method of typeHandleEvent

public FishingRod() { Assembly assembly = Assembly.GetExecutingAssembly();
foreach (var type in assembly.GetTypes()) { if
(typeof(IEventHandler).IsAssignableFrom(type))// Judge whether the current type is implementedIEventHandler 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 isFishingEventData, Event source matching if
(eventDataType.Equals(typeof(FishingEventData))) { // Create examples var handler =
Activator.CreateInstance(type) as IEventHandler<FishingEventData>; // Registration event
FishingEvent += handler.HandleEvent; } } } }
such, We can remove the display registration code from the scene classfishingRod.FishingEvent += new
FishingEventHandler().HandleEvent;.

4.2.3. Relieving dependence

How to release 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 aPublisher andSubscriber Bridge in the middle. It's isolated.Publlisher andSubscriber 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!!!

Analysis, IfEventBus 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 implementedIEventHandler 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, Cancellation of registration, Event triggering. 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 itDemo, modifyFishingRod Event triggered by:
/// <summary> /// Lower hook /// </summary> public void ThrowHook(FishingMan man) {
Console.WriteLine(" Start the next hook.!"); // 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: Tinkling, 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, Diligent in hands, 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>