views:

582

answers:

5

I'm reading the chapter on the Decorator Pattern in Head First Design Patterns. They use a coffee ordering system as an example to teach the pattern. While the Decorator Pattern seems a useful pattern, it does not seem appropriate for a coffee ordering system.

Wouldn't it be simpler to just have a table (or list or some such) of items (flavors, condiments, etc.) and costs (possibly with modifications for size)? When customer orders a Dark Roast with Mocha and Whip, create a list of items, then lookup and total the costs. Why go through all the class overhead? Adding a new item and cost to a list seems simpler than creating a new class.

Am I missing something basic? Should I be ignoring the example and just focus on the pattern? Should I be reading another book?

+2  A: 

Its just an example to illustrate the principle.

But please feel free to share your code here so we can comment on it.

Martin York
I'd code as I said. If someone orders X,Y,Z, I'd pass the collection X,Y,Z to a method, iterate through each part, look up each price and total the prices.
foosion
That sounds more error prone. For each public interface on your drink you must pass a set of iterators to define its content.
Martin York
(1) Create a list of what is wanted: ['roast', 'mocha', 'whip'] and pass to pricing method, (2) pricing method: foreach item in order_list, lookup price in pricelist table (implement as a database lookup, list of tuples, etc.) (3) we get back $1.50, $0.50, $0.50, (4) total and report. How's that error prone?
foosion
English is such a convenient language for hiding complexity. Implement it both ways for yourself. You will quickly see the difference in elegance.
Martin York
In python, I can write a pricing function that does what I described in less than 10 simple lines. It's a function, not a class. I did a list of {item, price} for the pricelist.
foosion
+1  A: 

The point was that the decorator – in contrast to your list – can apply arbitrary transformations on the price.

For example, have a look at the SpecialOfTheDayDrink which has a 20% discount:

class SpecialOfTheDayDrink extends Beverage {
    private final Beverage theDrink;

    public SpecialOfTheDayDrink(Drink theDrink) {
        this.theDrink = theDrink;
    }

    @Override
    public double cost() {
        return theDrink.cost() * 0.8;
    }
}

How would you model this?

(Admittedly, restricting the example in the book to a CondimentDecorator is misleading because here all cost calculations take the same form, as does the description. Other patterns would be more appropriate to model this.)

Konrad Rudolph
Is the same size multiple applied to every drink type (e.g., large is always 20% more than normal)? If so I'd just look up the size multiple and apply it to the order. If not, I'd store a price for each item.
foosion
"Other patterns would be more appropriate to model this." As I said in a comment to Adamski, that's a very large part of my issue. If the authors can't come up with an example for which this is the most appropriate pattern, is it really a very useful pattern? Maybe I'm just being too picky.
foosion
*Where* would you look up the size multiple? And the specials discount? And the condiment additions? Maintaining a bunch of tables for all that while making it extensible surely is a lot *more* work than the above decorator.
Konrad Rudolph
In practice, in a database. For an example program, in a suitable collection, which would depend on the language I was using.
foosion
@foosion: I think you misunderstood me, or I you. How would you store *arbitrary business logic* in a database? Of course, you can store discounts and item costs there. But you cannot (well) store arbitrary operations such as “add 15p” or “discount 20%” since the involve different operations on the price: adding versus multiplication. Of course you could store two separate tables for discounts and condiments. But that’s still a hard-coded business decision and nobody using your API could extend that to suit their purposes.
Konrad Rudolph
@Konrad, I never discount the possibility of misunderstanding, especially online. You could store (operation, amount) in a database if you wanted. Arbitrary business logic is harder, but if you can't store it in a database, it would seem almost any approach is going to involve changing code. My corner grocery sells OJ for $3.99 apiece, 2 for $6.00, and their register prices it immediately. I wonder how much work is required for this in any system. We do seem to agree that, in your words "Other patterns would be more appropriate to model this." If you haven't already, see Joey's answer
foosion
@Konrad, if you have the book handy, see the code on page 107, in particular the hard-coded business logic in the if-else suite.
foosion
+4  A: 

As Martin mentions it's just an example to illustrate the principle so you shouldn't get too hung up on whether it's appropriate.

A better practical example of usage of the Decorator pattern IMHO is within the Java JDK: The java.io package contains numerous InputStream and OutputStream implementations, each of which is typically constructed by passing in (i.e. wrapping or decorating) another InputStream / OutputStream instance. Hence it is possible to create a FileOutputStream and decorate it with a GZipOutputStream in order to write compressed data to a file.

Adamski
I'm happier if the example a book gives for a technique makes me think "wow - that's a great way to deal with the problem" rather than "there seems to be a better way. If the authors can't think of a wow example, maybe it's not a very good technique."
foosion
On the other hand, a too complex example would make the pattern more difficult to understand. Personally I'd rather have a concise example that let me concentrate on the structure and a clear explanation, that a 'real world' example which had complexity. I guess it's a matter of tolerance/expectation.
Matthieu M.
FileOutputStream is a subclass of OutputStream, not a decorator. The Decorator pattern allows you to add methods to a class without subclassing. The pattern also allows you to add methods to a final class. It's not always easy to apply in Java.
Mark Lutton
@Mark: I don't fully understand your comment: Just because FileOutputStream is a subclass of OutputStream this doesn't mean it is not a decorator, as a decorator is a pattern, not a specific class. My example was illustrating how you can wrap OutputStreams in order to enrich functionality, which *is* a valid example of the decorator pattern; for example constructing a FileOutputStream and then wrapping it in a BufferedOutputStream and finally a GZipOutputStream.
Adamski
+1  A: 

Decorator pattern is a pattern to add new functionality/feature to your existing object. You can google for many more examples on decorator pattern. You can start @ http://en.wikipedia.org/wiki/Decorator_pattern

sdp07
+3  A: 

Reading your question, I remember thinking the exact same thing when I read the chapter. Even with the use of decorators, you're still going to have to add a lot of code whenever you add a new beverage to the menu.

There's also the matter that there isn't really much difference among the beverages. Mochas, dark roasts, decafs and so on just aren't different enough to justify defining a whole new class for each one. As far as the business of the coffee shop is concerned, the drinks don't differ in behaviour; they differ only when it comes to properties: name, size, condiments, price, discount and so on. "Same behaviour, differing properties" is a hint that those objects should probably be of the same class.

A real-world chain of cafes isn't going to tolerate a code update every time they add a new drink to the menu or change the prices of existing drinks (because real-world economics make real-world coffee prices fluctuate). foosion's right: the real-world way to implement this is to use an approach that's more data-driven. Adding a drink to the menu should be done by adding a new row to the data table. Changing prices for a drink should be done by changing the "price" column for that drink.

As for the use of the "Starbuzz Coffee" example in Head First Design Patterns, it was probably used because one of the cardinal rules of the Head First series is that its content be interesting and fun, and the authors probably felt that the equipment inventory example for decorators in the Gang of Four book would bore their readers.

[Edit: Wouldn't you know it -- someone beat me to the punch by five years, saying pretty much the same thing -- see Ravi Venkataraman's comment to this article.]

Joey deVilla
"As for the use of the "Starbuzz Coffee" example in Head First Design Patterns, it was probably used because one of the cardinal rules of the Head First series is that its content be interesting and fun" Sounds right. As I mentioned in other comments, I'm happier if a book illustration presents a more compelling case. At the moment, I'm with you and Ravi. I'll withhold judgment pending reading GoF and more in this thread.
foosion
It appears Jeff Atwood agrees with us: "It's kind of damning that even the radically simplified pattern examples in the book are far more complicated than they need to be. Would I really design a point of sale system that used the Decorator pattern to represent coffee pricing? I think I'd use a simple relational database table and some procedural code. If I needed to add a topping, I'd simply add a record to the table-- no complex objects or inheritance models required." http://www.codinghorror.com/blog/archives/000200.html
foosion