Rethinking Decorators

In this article, writes Hal Helms, Ben Edwards (of Mach-II fame) and I rethink the Decorator design pattern.

Hal: So, did you see my CFDJ article on decorators?

Ben: Yeah. I saw it.

Hal: So, what did you think?

Ben: Let's just leave it at that: I saw it.

Ben and I were talking about my earlier CFDJ article on the Decorator design pattern [cfdj, vol. 6 issue 11]. In the article, I explained how the Decorator pattern could be used in situations in which there exists a base class with various options. In my article, I used the example of a base Coffee object with options such as ExpressoShot, WhippedCream, and FlavoredSyrup. The Decorator is useful in avoiding the sort of "class explosion" that would occur if we were forced to create separate classes reflecting all the possible permutations of base class with options: CoffeeWithExpressoShot, CoffeeWithWhippedCream, CoffeeWithExpressoShotAndWhippedCream, CoffeeWithExpressoShotAndWhippedCreamAndFlavoredSyrup, etc.

Ben: I'm not a big fan of Decorator as it's applied here.

Hal: What are your objections to it?

Ben: In order to have the Decorator work, as you explained in your article, you have to have all the options as subtypes of the base class.

Hal: Right. In the article example, FlavoredSyrup, WhippedCream, and ExpressoShot all extend the base Coffee class.

Ben: Making for an inheritance relationship.

Hal: Right.

Ben: And what's the test for creating inheritance relationships?

Hal: That the subclass "is-a" type of the base class.

Ben: And you consider WhippedCream to be a type of Coffee?

Hal: Well...no.

Ben: And, presumably, you'd need other options like ExtraHot, LowFoam, etc?

Hal: Right.

Ben: And those things aren't Coffee subtypes, are they?

Hal: No.

Ben: Well, that's my objection. It's not the best use of inheritance.

Hal: Yes, I see your point, but...

Ben: But what?

Hal: But it's such a danged useful pattern, I guess I'm willing to forgive a little impurity. It's a little like watching a movie. Typically, there's one presupposition that you have to accept - even one that may be a little far-fetched. You suspend disbelief just a little - and then you can accept the movie on its own terms. I guess I feel that way about the Decorator pattern.

Ben: The Decorator pattern was initially introduced by the "Gang of Four" in their book, Design Patterns. That was also the book that laid out the very useful, very good principle: "Prefer composition to inheritance." Composition exists when one class holds as an instance variable member(s) of another class. And that same good principle can be used to solve the problem of a base class with options. Using composition, we can keep separate the coffee from the extras you can add to it. I'd have the Coffee class composite its own options. See the way it's shown in Figure 1:

Ben: In this example the extras are added to the coffee rather than creating a wrapper around it. The coffee's description and price, for instance, are derived from the coffee itself and from the extras it has had added to it.

Hal: Yes, that would certainly work. What are the benefits of this approach over using the Decorator pattern?

Ben: Well, for one thing, the model conforms to the real world more closely than does the Decorator, where you end up with the bizarre situation where WhippedCream is a type of Coffee. As a matter of fact, the other day at the local Starboard coffee shop, I tried to order a cup of whipped cream. They wouldn't let me get whipped cream by itself; I could only get it in addition to a cup of coffee. They insisted that's why the coffees are separated on the menu from the "extras" - an "extra" is not a coffee. Separating the two better encapsulates the intent of each, providing more flexibility.

Hal: Agreed. Any other benefits?

Ben: You can tell the difference between the base coffee and the extras. With the Decorator pattern, the base class is subsumed by the Decorator class(es). I always know that I have a cup of Coffee - that has some extras added to it.

Hal: Any more benefits?

Ben: You don't run the risk that Decorators will override either a method of either the base class or one of the "inner" decorators.

Hal: I suppose that's something of a double-edged sword. Decorators do provide you with the ability to override their base class's methods while composition doesn't. You actually might want that.

Ben: You could, yes. And finally, with composition, you can easily remove an extra -something you can't do (or at least can't do easily) with Decorators.

Hal: Those are good points...

Ben: But you're unconvinced.

Hal: Well, I can see the benefits you've laid out, but I fundamentally don't like the idea that Coffee has to know about its extras. It seems that Coffee should only concern itself with itself - not with what might be added to or done with it. If Coffee has that responsibility, I worry that future needs will force me to alter the code for Coffee, rather than add a Decorator. That, for me, is a great benefit of using Decorators - the base class is only responsible for itself. It provides a layer of abstraction - and you know how I love abstractions. Now, you mentioned the case of an extra-hot coffee. In your scheme, would ExtraHot be a "CoffeeExtra" as you've defined it?

Ben: That does seem to be stretching things.

Hal:But a Decorator could handle it easily.

Ben: I agree: both patterns have their advantages and disadvantages. This example isn't a replacement for the Decorator pattern, but a different solution for this context.

Hal: Maybe that's the central point, Ben: when developers learn design patterns, they need to understand that they're not magic bullets. A pattern may be perfectly good in one context, but not well suited for another. I've seen developers force patterns into their code just for the sake of buzzword compliance.

Ben: Yeah, points will be deducted for gratuitous use of design patterns! Also, one pattern may just fit better with a developer's preferences. You're troubled by the idea that Coffee knows about its extras while it seems completely right to me that a Coffee should take on this responsibility. The point of learning design patterns isn't so that you can slavishly use them, but that you gain a wider perspective on how to solve problems in an elegant way that allows code to evolve gracefully.

Hal: So, one thing we can both agree to is that I'm right.

Ben: You said it: I'm right.

© 2008 SYS-CON Media