Previously, I wrote about how events provide you with flexibility in your code. If you take on an event-based paradigm, you can view your system as a group of components that have events execute when certain conditions are met, and less of a procedural view where X always must occur after Y. But what else do events let us do? Decouple your architecture!
How Events Decouple Your Code
So the big question then is, how? I'd like to start by providing framing an example architecture. If we assume that we have code that is decoupled by major functionality, we might have some sort of layered architecture. This could mean that we have three layers: presentation, application, and data. These layers would be responsible for user interaction, core business logic, and data storage/retrieval respectively. Let us also assume that the knowledge of layers flows from the top down. In our example, the data layer knows only of itself, the application layer knows about itself and the data layer, and the presentation layer has access to all three. It's essentially just the flow of dependencies. Great. So where is this going?
If you've structured the various components in your system to take advantage of events, C# events provide us with a pretty awesome signature: you get the sender of the event, and a little magical parcel of data called EventArgs. Now let's recall that in my previous posting, I mention that an event is really just a type of delegate. What does it mean to delegate? Literally, what does it mean? The great Google tells me: "Entrust (a task or responsibility) to another person, typically one who is less senior than oneself". There. The secret of decoupling. My code can't take responsibility for doing something, so I should tell whoever is interested that now might be a great time to do something.
Let's See An Example!
Let's continue off of the sample architecture that I already started off with. So we have our three layers. Let's pretend we have some sort of import process where we read in lines of a file. Every line in the file is supposed to have two values, separated by a comma. This is all done by an importer class in our data later. Sounds easy.
Now, I'm not suggesting this is something you'd actually want in your application, it's simply to demonstrate my point. So let's pretend that the source of our data could sometimes be poorly formatted. Our import function can easily know about this (i.e., it splits the line on a comma, and finds out that there are not exactly two values). But does it know how to resolve it? Is it supposed to skip it? Is it supposed to magically figure out how to fix it? Does it crash and burn? Wait... Maybe it's not responsible for making that decision... So who is?
Invoke that event! If your importer class has an event, let's say,
LineFormatError, then someone on the outside can subscribe to it. Maybe my presentation layer can subscribe to it and, through event args, tell the invoker of the event (my importer class reference, of course) how to handle it. The flow could look something like this:
- Hook your necessary events up, so something in the presentation layer hooks on to my importer's event for line format errors.
- Start your import.
- Upon hitting a poorly formatted line, invoke the
- The presentation layer receives the event (so now we're in the "event handler").
- The presentation layer could use information provided in the event args to ask the user if they want to skip the line.
- Based on the user's decision, the presentation layer sets some information on the event arguments that says whether or not to skip it.
- The control goes back to the data layer where it checks the event args for what to do--skip or abort.
How is this decoupled? Well, quite simply, you don't have your data layer directly showing message boxes to the user to decide what to do. It has absolutely no idea that there's even a message box being shown... it doesn't care! It simply agrees that it's not responsible for making the decision of whether or not to skip or abort, but lets someone else handle it. It delegates the decision through an event.
Events are beautiful things. Leverage them, and you can take advantage of some really decoupled code. Your various components often can't be responsible for certain tasks, but using events, these components can delegate the decisions to other parts of your code that are certainly responsible for making the decision.