-
Notifications
You must be signed in to change notification settings - Fork 4
DawnNotifications
The notification system in Dawn was built to address the same problems many other libraries try to address with notifications, i.e. communicating information around a system without coupling parts of the system that need not have any knowledge of each other. Unfortunately lots of those notifications systems tend to make me grumpy!
I wanted a notification system that adhered to the DesignPrinciples, that helped me achieve type safety (where possible), allowed me to use closures easily and didn’t force me define constant strings all over my application.
With that in mind I built Dawn notification system with three core ideas
- Type Based
- Closure Friendly
- Static Free
There are two ways to use the notification system in Dawn. The simplest (and most common) way is the callback interface, and the second involves defining a contract between the notification and a handler object. The second method is very valuable if you don’t want the listener to have any knowledge of the notification system
Callbacks are the tersest and most common way to use the INotificationBus
. The API is intentionally simple. Classes that want to know about something happening in the system listen for notifications of the type which represent that action taking place.
Lets say we have an application that picks apples… and there are lots of separate parts of the application that need to know when an apple is picked.
We can represent the action of an apple being picked with an object (a notification). There is nothing special about the object, it doesn’t need to extend a base class or implement an interface, it’s just an object that represents the action of apple picking.
class ApplePicked { public var apple:Apple; public function ApplePicked(apple:Apple) { this.apple = apple; } }
Since it’s an object we can include information in it, so this notification also carries a reference to the apple that was picked.
Now lets look how that notification might be sent by the system… by a fruit picker!
class FruitPicker { private var bus:INotificationBus; public function FruitPicker(bus:INotificationBus) { this.bus = bus; } public function pickSomeFruit():void { // perform some fruit picking... get an apple var apple:Apple = getAppleFromTree(); // inform the rest of the world we picked an apple bus.trigger(new ApplePicked(apple)); } // other fruity methods ... }
When the FruitPicker
picks an apple from a tree it use its instance of INotificationBus
to send (trigger) a new notification of type ApplePicked
. The FruitPicker
doesn’t care what the rest of the application does with the information.
OK that’s triggering, now lets see how other parts of the application listen out for that notification:
class DeliveryTruck { private var bus:INotificationBus; private var cargo:Array = []; public function DeliveryTruck(bus:INotificationBus) { this.bus = bus; bus.addCallback(ApplePicked, onApplePicked); } private function onApplePicked(applePicked:ApplePicked) { cargo.push(applePicked.apple); } }
Ta Da! Now we have a DeliveryTruck
class that is storing all of the apples that have been picked. In order to be notified whenever an apple was picked it added a callback to the notification bus (the same instance of INotificationBus
as the FruitPicker
has). Adding callbacks really is that simple: pass in the object type you are interested in, a closure with one parameter of the type which you are listening for.. and you’re away!
Righto… now lets see what happens when our fruit pickers decide to diversify, and start picking pears as well!
Here’s a newly-upgraded fruit picker:
class FruitPicker { private var bus:INotificationBus; public function FruitPicker(bus:INotificationBus) { this.bus = bus; } public function pickSomeFruit():void { if( getCurrentTree() is AppleTree ) { var apple:Apple = getAppleFromTree(); bus.trigger(new ApplePicked(apple)); } else { var pear:Pear = getPearFromTree(); bus.trigger(new PearPicked(pear)); } } // other fruity methods ... }
At this point we could go into our DeliveryTruck
and add a second listener for the new PearPicked
notification, but that doesn’t sound like it’s going to scale very well: what happens when those excited pickers start finding strawberries and the like.. you get the picture!
This is a simple example of why using types to register for notifications is far more powerful that using strings (ick)! I’m going to create a interface that I want both ApplePicked
and PearPicked
to implement
interface IFruitPicked { function get fruit():Fruit; }
Here’s the modified ApplePicked
class (btw, both Apple
and Pear
extend Fruit
, of course!)
class ApplePicked implements IFruitPicked { public var apple:Apple; public function ApplePicked(apple:Apple) { this.apple = apple; } public function get fruit():Fruit { return apple; } }
Now lets see what we can do in the DeliveryTruck
class DeliveryTruck { private var bus:INotificationBus; private var cargo:Array = []; public function DeliveryTruck(bus:INotificationBus) { this.bus = bus; bus.addCallback(IFruitPicked, onFruitPicked); } private function onFruitPicked(fruitPicked:IFruitPicked) { cargo.push(fruitPicked.fruit); } }
That simple change allows the DeliveryTruck
to listen to notifications of many types! If this still isn’t clear, look back at the newly-upgraded FruitPicker class. It can trigger notifications which send either a new ApplePicked instance, or a new PearPicked instance. Because they are both of type IFruitPicked, the DeliveryTruck’s onFruitPicked method will be called with each instance. It’s a polymorphic WIN!!!