Pattern Hatching

Multicast - Observer = Typed Message

John Vlissides

C++ Report, November/December 1997

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. . . .

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 Note that Mb and Oc are very similar, as are Oa and Mc.

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.

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.

ONE MORE DATAPOINT

Beyond the specter of never reaching closure, what worried me most about this debate was the small sample set of opinions. So I was delighted when Bob Martin volunteered his thoughts to me—even before he uttered them, because I know how firmly ensconced he is on the strong typing side . . .
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.

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.

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.

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 dependency—exactly Bob's example and clearly outside Observer's intent.

ABOUT FACE

Q.E.D., or so I thought. Leave it to Erich to snatch defeat from the jaws of victory:
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.

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."

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.

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 here—a 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.

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.

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.

A STRAWMAN

Okay, but what does this new pattern look like? Quite similar to Multicast, actually—at least as of this writing. We are admittedly a long way from a polished Typed Message. Here in the meantime is a sketch of the current strawman.

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.]

Structure

See Figure 1.



Figure 1

Participants

Message (ProductDispensedEvent)

Sender (Dispenser)

AbstractReceiver (ProductDispensedHandler)

Receiver (CoinChanger)

Collaborations

Consequences

  1. Information may be passed type-safely and extensibly without downcasts or switch statements...
  2. Supports implicit invocation when combined with OBSERVER...
  3. Lack of multiple (interface) inheritance makes application difficult...
  4. May lead to convoluted inheritance graphs...

Implementation

  1. There needn't be a one-to-one relationship between Message classes and AbstractReceiver interfaces...
  2. Lumping Message and Sender improves extensibility but makes it harder to tell who sends a given Message...
  3. Implementing TYPED MESSAGE in a weakly typed language or one lacking multiple inheritance...
  4. Combining TYPED MESSAGE and OBSERVER...
  5. 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.

MORE TO COME

You may have forgotten by now, but an overarching goal in all this is to support new events with minimal change to existing code. We've made considerable progress towards this goal by designing with patterns old and new. But what if you're dealing with a system that wasn't designed with this goal in mind, a system you couldn't change if you wanted to?

Well, suppose you could make that system generate an event when any one of its methods got called—without 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.

MAILBAG

Mark Betz writes,
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 circumstances—after 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:
    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();
    }
    
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!)

ACKNOWLEDGMENTS

Thanks again to Erich, Richard, and Ralph for letting me quote them out of context, and to Mark and Paul for taking the time to write.

Footnotes

*An aspect is an object that specifies the change precisely. Supplying an aspect along with the notification can make updating much easier for the observer. Design Patterns mentions aspects in item 7 on page 298.

**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!

References

1 Vlissides, J. "Pattern Hatching," C++ Report, September 1997.

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/.


©1997 by John Vlissides. All Rights Reserved.