Announcements Framework

Advanced Announcement Handling, part 1

December 4, 2005 1:30:20.936

Announcements-related methods in Object implement only the bread-and-butter stuff--the basic subscribing, unsubscribing and announcing functions we've discussed so far. For anything advanced (read less frequently used) you need to go to the object's subscription registry. One reason for this separation is to avoid (and in the distant future when triggerEvent is retired, reduce) Object's API bloat. triggerEvent has 44 methods in Object, plus about 15 #evaluate and #evaluateWithArguments: sprinkled around various classes. All the current version of Announcements has "out in the open" are 13 methods in Object.

The registry is the gateway to the rest of announcement API. This is another important point--it's a gateway rather than just the container of the rest of the API. In Announcements, the API is not simply split in two with the less frequently used stuff moved out of Object. The overall API design is different from triggerEvent in that it's focused on capturing the key concepts as objects you can work with, rather just adding a bunch of methods to Object for the functions the users are expected to need. This leads to a smaller API that goes much further.

If this still sounds vague at this point, I hope this and the following posts will illustrate what I mean. Also note that if you want to look at the actual code, for this and the following posts you'll need an up-to-date version (at least "preview-13.0").

So, suppose we want to do some advanced stuff and we got a hold of an object's registry.

To start with the simplest thing, you can send messages #isEmpty and #notEmpty to it to find out if it has any subscriptions--in case you want to do something differently when it doesn't compared to when it does. (I don't know why you might want to do that, but Travis does, so ask him if you are curious).

Most important in the grand scheme of things are the four selection messages that select the currently existing subscriptions:

	allSubscriptions
	subscriptionsFor: announcementClassOrSet
	subscriptionsOf: anObject
	subscriptionsOf: anObject for: announcementClassOrSet

These are fairly self-explanatory. "Of:" means we want subscriptions where the object we specify is the subscriber, "for:" means we want subscriptions for the specific announcement class or class set, and both mean we combine both of those conditions.

Once we've found the subscriptions we are interested in, we can do a number of things with them. One is we can unsubscribe from them. As you remember, Object's #unsubscribe: and #unsubscribe:from: handle the two most common cases: you can say

	anObject unsubscribe: self

or

	anObject unsubscribe: self from: Foo

Somewhere down below these actually do

	registry removeSubscriptions:
		(registry subscriptionsOf: self)

and

	registry removeSubscriptions:
		(registry subscriptionsOf: self for: Foo)

Registry API enables less considerate unsubscribing options, those that affect multiple subscribers at once and for that reason are not included in the basic API in Object:

	registry removeSubscriptions: registry allSubscriptions

zaps all subscriptions no matter who subscribed and for what announcements. (Just trashing the whole registry with "anObject subscriptionRegistry: nil" might work but isn't nice, because as we'll see in future posts, the registry might not be an object you can just throw away. Incidentally, #subscriptionRegistry: is a private method).

	registry removeSubscriptions: 
		(registry subscriptionsFor: Foo)

removes all subscriptions for the announcement class Foo, no matter the subscriber.

So far this might not have looked very impressive. After all, one subscription removal method combinable with four selection methods sounds nice in theory but in practice, why not just make the registry understand #removeAllSubscriptions, #removeSubscriptionsFor: and all those other cases?

It's because elevating subscription selection to the level of public API (and making subscriptions real objects in the first place) gives us tremendous flexibility, nearly for free. Even before we get to other advanced things in followup posts, the simple fact that subscriptions are objects and we can work with their collections allows the framework user to "just do" things triggerEvent had to provide for specifically. Or not provide at all.

For example, in triggerEvent there is a method Object>>hasActionForEvent: to test, in Announcements speak, whether an object has any subscriptions for an event. Why, finding this out with Announcements is as simple as

	(registry subscriptionsFor: Foo) isEmpty

Or just as easily we can do

	(registry subscriptionsOf: anObject) isEmpty

to check whether a particular object is a subscriber--something triggerEvent doesn't do.

Or to find out what announcement classes are in demand at the moment:

	(registry allSubscriptions
		collect: [:each | each announcementClass]) asSet

Or, similarly, to get a collection of all the current subscribers:

	(registry allSubscriptions
		collect: [:each | each subscriber]) asSet

Or we can remove all subscriptions whose subscribers we don't like for whatever reason:

	registry removeSubscriptions:
		(registry allSubscriptions select: 
			[:each | self dislikes: each subscriber])

or the same thing, but like this:

	registry allSubscriptions do:
		[:each |
		(self dislikes: each subscriber) ifTrue:
			[registry removeSubscription: each]]

I hope this gives a a taste of "capturing the key concepts as objects you can work with, rather just adding a bunch of methods to Object for the functions the users are expected to need".

To be continued.