In my opinion, Multicast is a special case of Observer, a more complicated case. This is not a proof that there shouldn't be two patterns in the catalog, just something to consider when you are deciding what should be there.My last column1 ended with that quote from Ralph Johnson. It sums up one side of the controversy surrounding Multicast, a not-ready-for-prime-time pattern I've been mulling for a couple of columns now. The opposing viewpoint maintains that Multicast and Observer are indeed related, but they're different enough to warrant independent treatments. Notably, the battle line demarcates typing philosophies as well, with proponents of weakly typed languages on one side and those favoring strong typing on the other. Strong typers prefer to make Multicast its own pattern; weak typers think it's a logical extension of Observer. Among the Gang of Four, Ralph is the lone weak-typer.
The waning arguments for an independent Multicast pattern came from Richard:
I still believe that Observer/Multicast are different but related patterns. . . . Think about what concepts are varying: in Observer, it really is the concrete observers, and possibly the aspects* of the subjects. In Multicast, it is the types of events. To me this is the key difference between the two patterns. I do not see Multicast as an extension to Observer or vice-versa for this reason. . . .Erudite as this may be, Ralph took issue with almost all of it. The details aren't important; suffice it to say we were at an impasse. When our discussions reach such a quasi-mathematical level, I know the end is near, one way or the other. Happily, an amicable resolution was just around the corner . . . but I'm getting ahead of myself.Note that in common to both patterns there is the solution concept of registration and notification. But this is basically a mechanism to allow senders and receivers to be bound at run-time. It is not intrinsic to the problem each pattern is solving but to the fact that these patterns establish relationships between objects, and registration/notification is a basic mechanism (pattern?) to do this.
[Think also about] scope and variations. In this light I think that the variations to Multicast include
Similarly, variations on Observer include
- Ma: global notification, registering with the event class, e.g., MyEvent::register(MyEventHandler)"broadcast" as per John's article.
- Mb: local notification, registering with the sender, e.g., Sender::registerMyEventHandler(MyEventHandler) as per Erich's preference and closer to my idea of "narrowcast."
- Mc: local notification, registering with the sender and using a single event class, e.g., Event.
Note that Mb and Oc are very similar, as are Oa and Mc.
- Oa: notification of nonspecific change (naive observer), e.g., Observer::Update().
- Ob: notification of semispecific change, e.g., hintsSubject::Register(Aspect, Observer).
- Oc: notification of specific change, e.g., an eventSubject::registerMyEvent(MyEventHandler).
In this perspective, (a) Observer is a refinement of Multicast, but equally, (b) Multicast is a refinement of Observer. It is just that with (a) you have limited the scope of events more and more to arrive at a specialized Observer. With (b) you have extended the scope of Subject changes in Observer to arrive at a specialized Multicast. Both views are valid.
Exactly the same phenomenon [occurs] when you push traversals into the Visitor, [producing something] close to Iterator. Or you extend Strategy to arrive at Builder.
I always like to start with the naive patterns and see what kinds of changes you have to make to arrive at another pattern. From the above you have to go from Ma to Mb to Mc to Oa to get from Multicast to Observer. I have always maintained that wisdom about patterns comes from the space between them.
I wanted to respond to the issue you raised in your Multicast article. Is Multicast just a variation on Observer? I think not. And the reason that I think not is that observers know their subjects. But Multicast handlers don't have to know their event sources.The keyboard-versus-keypad example is particularly germane. It reinforces a point I made earlier1, when I compared the relationship between Multicast and Observer to that of Abstract Factory and Factory Method. The most common implementation of Abstract Factory uses a Factory Method for each product2, but that doesn't mean we make Abstract Factory an extension of Factory Method. They are different patterns because they have fundamentally different intents.In Observer, you want to know when the state of something changes. So you register an observer on that thing. But in Multicast you are interested in the occurrence of a particular event. You don't care about the source of that event. (Which, by the way, is why I liked your idea about putting the registration function in the Event rather than Erich's notion of putting the registration interface on the event source.)**
Consider a keyboard event. We may have a system with a keyboard and a keypad. Both these devices produce keyboard events. The software doesn't care about the source. It doesn't care that the "3" key was hit on the keypad or on the keyboard, it just wants to know what keys were hit. We could make the same argument using a mouse and a joystick, or anything where there might be multiple event sources.
I think this is a fundamental difference between Multicast and Observer. In Multicast we are still observing something, but we don't know what we are observing. The thing we register with is not the thing that generates the stimuli that we are interested in.
Similarly, viewing Multicast as an extension of Observer runs counter to their respective purposes. Observer maintains a one-to-many dependency between objects, while Multicast is about delivering encapsulated information to objects in a type-safe and yet extensible fashion. To underscore the difference, I posited using Multicast to implement a many-to-one dependencyexactly Bob's example and clearly outside Observer's intent.
In my Design Patterns and Java course, I had to explain the new JDK 1.1 event handling model. So it all came back to me again. It turns out in explaining the design that it was really easy to [introduce] this as an Observer relationship, since everybody was familiar with that. In the next step I explained the refinements on typed events and registering interests. So I think it is important to state that Multicast is a refinement of Observer and [to describe] what it refines instead of arguing whether Multicast is different from Observer. This doesn't answer the question whether a refinement deserves its own pattern. I still think it does.One might say Erich was speaking heresy, the assertion at the end of the first paragraph notwithstanding. It was in retrospect the last gasp of Multicast as we knew it.Actually, there is another example of this in GOF. At some point we argued whether Builder isn't just a Strategy for creating things. Obviously Builder made into the book as a separate pattern.
Regarding the name, after reading the arguments about types. I'm getting attracted by the name "Typed Message."
I for one was taken aback, to be sure, but in a good way. Things finally seemed to be clicking. But like any prudent soul contemplating a one-eighty, and ever the bottom-liner, I wanted to make certain I understood the ramifications of this epiphany:
So you're advocating stripping registration and delivery out of Multicast, putting it into Observer, and saving the rest for Typed Message?The reply was exceptionally quick.
Yes, I think this is it. Remember that we already discuss the registration for interests in Observer (see page 298, bullet 7). The more I think about it, the more I like it . . .Lest the import be lost on you, mellow, even-tempered Erich is expressing excitement herea propitious sign.
Ralph gave his blessing shortly thereafter:
I would be a lot happier with Multicast/Typed Message if it were presented the way Erich said. The relationship to Observer is so obvious that when we don't emphasize it, it makes me think we are trying to hide something.Well now, those long-standing battle lines suddenly seem quaint and irrelevant. We would extend Observer only as necessary to account for a new pattern, Typed Message. The synergy between the two would render a separate Multicast pattern redundant.When we wrote our book, we were trying to hide something. We were trying to avoid talking about one pattern being a specialization of another, or one pattern containing another as a component. We wanted to avoid going meta and just wanted to talk about patterns. This was part of why we didn't describe abstract class as a pattern, since most of the patterns would have contained it.
I think that was a good decision for the first patterns catalog, but the world is different now. People want to know the relationship between patterns and we need to tell them. The relationship here is so obvious that we need to emphasize it, not just tuck it away at the end in the "related patterns" section.
Intent
Encapsulate information in an object to allow its type-safe transmission. Clients may extend the object to add information without compromising type safety.
Motivation
[Basically the vending machine example from Multicast1 with minor changes to emphasize the encapsulation and extension of events and to de-emphsize the notification process. Some of the more detailed code has been moved to the Sample Code section.]
Applicability
Use TYPED MESSAGE when all of the following are true:[Exactly the same as before.]
- Certain classes of objects may be interested in receiving information from other objects.
- The information is of arbitrary structure and complexity and may vary as the software evolves.
- The information transfer should be statically type-safe.
Structure
See Figure 1.
Participants
Message (ProductDispensedEvent)
- Encapsulates information to be transferred from Sender to Receiver.
Sender (Dispenser)
- Maintains a reference to a Receiver object.
- Implements one or more operations that involve sending a message to the receiver.
AbstractReceiver (ProductDispensedHandler)
- Defines an interface for receiving a Message object.
Receiver (CoinChanger)
- Implements one or more AbstractReceiver interfaces.
Collaborations
- A sender instantiates a message and delivers it to its receiver.
- A message is passive; it does not initiate communication with senders or receivers.
Consequences
- Information may be passed type-safely and extensibly without downcasts or switch statements...
- Supports implicit invocation when combined with OBSERVER...
- Lack of multiple (interface) inheritance makes application difficult...
- May lead to convoluted inheritance graphs...
Implementation
- There needn't be a one-to-one relationship between Message classes and AbstractReceiver interfaces...
- Lumping Message and Sender improves extensibility but makes it harder to tell who sends a given Message...
- Implementing TYPED MESSAGE in a weakly typed language or one lacking multiple inheritance...
- Combining TYPED MESSAGE and OBSERVER...
- Trade-offs in defining a common base class for Messages...
Sample Code
[Code snippets lifted from the Motivation's vending machine example. Includes variant implementations such as the lumped Sender-Message design described last time1 and an implementation using composition instead of multiple inheritance.]
Known Uses
Java's JDK 1.1 uses TYPED MESSAGE in conjunction with OBSERVER in its "delegation-based event model," as Erich mentioned. I have used it in at least one project at IBM Research, but that work is as yet unpublished. Other known uses are most welcome!
Related Patterns
[OBSERVER is related, of course, but that will have been made abundantly clear by the time you get to this section.]Typed messages may be mistaken for commands (see COMMAND2). As always, the difference has to do with intent. A command encapsulates an operation, whereas a typed message encapsulates state. One is active, the other is passive. TYPED MESSAGE also has an emphasis on type-safe extension that COMMAND lacks.
TYPED MESSAGE may seem closer to a pattern that's often used with COMMAND, namely MEMENTO2. But MEMENTO's intent is the opposite of TYPED MESSAGE's: a memento must avoid transferring information to other objects. The information it encapsulates is for its originator's eyes only.
Well, suppose you could make that system generate an event when any one of its methods got calledwithout modifying the caller or callee one whit. Impossible you say? Next time we'll look at something called "subject-oriented programming"5 that adds a new dimension to these patterns.
In ["Latter-Day Events"3] you resolve the "type-laundering" issue as it applies to handling of events in the control framework. Decentralization is one solution, but it has an effect that I didn't see directly acknowledged: haven't you decentralized event handling until it was driven right out of the framework?Yes and no. The framework can still define event classes and handlers for common events. I mentioned two, TimerEvent and ErrorEvent. Decentralization doesn't preclude reuse; the two are orthogonal.
If event handling is to be type-safe, which was the point of the exercise, then the framework must avoid binding a particular type of event to the handling of that event. That's the goal of decentralization. When the framework defines the lone event-handling interface by binding event to handler, it must assume a lowest-common-denominator event type, and applications must make up the difference dynamically.
That may be a reasonable trade-off, by the way. Static typing is more of a hindrance than a help in many circumstancesafter all, 50,000 Smalltalkers can't be wrong!*** But the larger and longer-lived the system, the more likely it is to benefit from static typing.
Speaking of reuse, Paul Pelletier (unknowingly) applied Cope's Curiously Recurring Template Pattern3 to Multicast and (unknowingly) wound up with a streamlined way to implement Typed Message:
After reading ["Latter-Day Events,"] I thought, "Hey, a nice template could probably be created to simplify even more the creation of new events . . ." After playing with this idea for a while, I came up with the following code. The part I'm not too sure about is the way the template is used, using the derived class we're creating as an argument:Note that Typed Message comprises all but the registration and notification machinery of this implementation. (Hmm, more fodder for the Implementation and Sample Code sections. Bravo, Paul!)class CoinInsertedEvent : public TEvent<CoinInsertedEvent>I've never seen templates used this way, but I can see now that this could be a useful way to have the type checking done at compile time. Does this way of using a template has a special name?Another nice thing about using the template is that the Handler interface is automatically generated as part of the TEvent class itself, simplifying even more the addition of new events.
#include <iostream.h> #include <stdio.h> #include <list> using namespace std; template <class T> class TEvent { public: class Handler { public: Handler() { TEvent<T>::Register(this); } virtual int HandleEvent(const T &t) = 0; }; typedef list<Handler*> HandlerList; static void Register (Handler* aHandler) { registry.push_back(aHandler); } static void Notify (TEvent<T>* t) { HandlerList::iterator i; for (i = registry.begin(); i != registry.end(); i++) { (*i)->HandleEvent(*(T*) t); } } void Notify () { T::Notify(this); } private: static HandlerList registry; }; class CoinInsertedEvent : public TEvent<CoinInsertedEvent> { }; class CoinReleaseEvent : public TEvent<CoinReleaseEvent> { }; class ProductDispensedEvent : public TEvent<ProductDispensedEvent> { }; class CoinChanger : public CoinReleaseEvent::Handler, public ProductDispensedEvent::Handler { public: int HandleEvent (const ProductDispensedEvent &event) { cout << "Changer::Coin dispensed." << endl; return 0; } int HandleEvent (const CoinReleaseEvent &event) { cout << "Changer::Coin released." << endl; return 0; } }; TEvent<CoinInsertedEvent>::HandlerList TEvent<CoinInsertedEvent>::registry; TEvent<CoinReleaseEvent>::HandlerList TEvent<CoinReleaseEvent>::registry; TEvent<ProductDispensedEvent>::HandlerList TEvent<ProductDispensedEvent>::registry; int main (int, char**) { CoinReleaseEvent coinReleaseEvent; CoinChanger coinChanger; ProductDispensedEvent productDispensedEvent; coinReleaseEvent.Notify(); productDispensedEvent.Notify(); }
**Just to make Erich's position clear, he isn't opposed to putting the registration interface in the Event class. He simply wants the Motivation's exposition to reflect the general case that's described in subsequent sections.
***I pulled that number out of a hat. Please don't quote me!
2 Gamma, E., R. Helm, R. Johnson, J. Vlissides. Design
Patterns: Elements of Reusable Object-Oriented Software,
Addison-Wesley, Reading, MA, 1995.
3 Vlissides, J. "Pattern Hatching," C++ Report,
February 1997.
4 Coplien, J. "Column Without a Name," C++
Report, February '95.
5 http://www.research.ibm.com/sop/.
References
1 Vlissides, J. "Pattern Hatching," C++ Report,
September 1997.
©1997 by John Vlissides. All Rights Reserved.