Decorator pattern and concrete types

Decorator pattern and concrete types

Old forum URL: forums.lhotka.net/forums/t/1688.aspx


Michael Hildner posted on Sunday, November 05, 2006

Hi,

I don't really have a use case or anything, just a general question. Let's say you're using the decorator pattern, wrapping a bunch of behavior on your object. For example, you have a Car, which is wrapped with a Radio, which is wrapped with a SunRoof.

Say you've got code that needs to know one of the concrete types. For example, you'd like to know if a particular car has a radio, and if it does, turn it on.

I understand that the decorator pattern doesn't allow this to happen, but it seems like it could be useful. Are there any ways to get around this?

Thanks,

Mike

Bayu replied on Monday, November 06, 2006

Hey Mike,

You are right in stating that the decorator wraps behavior, it's a behavioral pattern. You sample however of Car - Radio - SunRoof is not about behavior. Your sample rather deals with composition, so you would have a look at structural patterns (e.g. Composite).

An excellent example of wrapped behavior can be found in the System.IO namespace of the dotnet framework. Most of the classes that inherit System.IO.Stream (perhaps even all of them) have constructors that allow you to pass another instance of Stream. The newly instantiated one will then wrap the provided one. This is about behavior as all the wrappers basically augment the 'byte  sequence processing'.

Another popular use of decorators in dotnet is when you apply Attributes (System.ComponentModel) to your member declarations. For example Rhocky's <RunLocal> attribute is in fact a decoration that 'wraps' the dp_fetch method. The beauty of using attributes is that you don't have to do the usual plumbing/overwriting/enhancing that comes with implementing wrapper subclasses, they are very lean and mean instead. ;-)

Just my 2 cents ...

Regards,
Bayu

Michael Hildner replied on Monday, November 06, 2006

That makes a lot of sense, thanks Bayu. I've seen the "car" example a few times discussing the decorator, but it never really made sense to me. Just plain seems wrong to say that a Radio is a Car.

I don't know if you've ever seen the book Head First Design Patterns, but in their decorator example, they use a Beverage base class - so you might have an Espresso wrapped with a Mocha wrapped with a WhippedCream. The only behavior the decorator objects add is cost. I don't know if that's a good example or not. Any thoughts? That too seemed a little wrong to me - I mean a condiment is a condiment and not a beverage.

The System.IO thing makes perfect sense.

Thanks,

Mike 

Bayu replied on Monday, November 06, 2006

Michael Hildner:

I don't know if you've ever seen the book Head First Design Patterns, but in their decorator example, they use a Beverage base class - so you might have an Espresso wrapped with a Mocha wrapped with a WhippedCream. The only behavior the decorator objects add is cost. I don't know if that's a good example or not. Any thoughts? That too seemed a little wrong to me - I mean a condiment is a condiment and not a beverage.



I don't know that particular book. I read the famous Design Patterns book by the Gang of Four.

It's a bit of an odd example indeed. Personally, I don't think I would ever use a decorator pattern to compute the price of a 'complex' product. The key example that clarified decorators to me was the Java windowing toolkit. It's been a while ago, so I may not use the correct naming, but they use a decorator pattern in their Panels. I believe you had a ScrollBarPanel which could decorate an arbitrary other panel and then it would simply augment that other panel with a scrollbar.

In a way perhaps the WhippedCream and Mocha 'augment' your Espresso. But like you said ... it looks like a far-fetched sample ......... .like using a drill to peel an egg .... ;-)

Bayu

Michael Hildner replied on Monday, November 06, 2006

I just ordered that book, thanks for the link. The ScrollBarPanel makes sense to me too - just like the System.IO.Stream does.

malloc1024 replied on Monday, November 06, 2006

A common use of using the decorator is to change the price of items.  A DiscountedItem object would decorate a LineItem object to apply a discount. 


The decorator pattern is about adding additional responsibility to an object.  Adding options to a car like a sunroof and radio is an acceptable way to use the decorator pattern.  For example, when you purchase a car, you could decorate the car with additional options (sunroof, radio etc.) to change the price of a car.


The Head First book is an excellent book.  The examples in that book are ridiculous on purpose.  It helps you remember the pattern.   People who often complain about the examples in the book don’t realize this.


 

Bayu replied on Monday, November 06, 2006

malloc1024:

A common use of using the decorator is to change the price of items.  A DiscountedItem object would decorate a LineItem object to apply a discount. 

The decorator pattern is about adding additional responsibility to an object.  Adding options to a car like a sunroof and radio is an acceptable way to use the decorator pattern.  For example, when you purchase a car, you could decorate the car with additional options (sunroof, radio etc.) to change the price of a car.

The Head First book is an excellent book.  The examples in that book are ridiculous on purpose.  It helps you remember the pattern.   People who often complain about the examples in the book don’t realize this.



Fair enough.

I never intended to say it was an 'unacceptable' use case for decorators. It may very well be a shortage of understanding/experience that causes me to see it as applying a drill to an egg. ;-)

Bayu


SonOfPirate replied on Monday, November 06, 2006

To further defend the Head First book, their example also needs a bit of context to understand.  They are not referring to WhippedCream as a condiment in as much as they were making light of the way in which coffee is ordered these days - where simply saying WhippedCream translates into the whole order.  They use the Alice and Mel's Diner example for this paradigm also.

To draw on malloc's example, think of this like walking into a car dealership and asking for the Ford Mustang GT versus the Shelby or (in my youth, at least) the Saline.  Beneath the surface, they are both Mustangs, but by specifying the "package", you've identified a set of options, extras, etc. that "decorate" the underlying object - the Mustang - and make it a Shelby or GT.

Back to the original post, though.  You are right, there is a difference between is-a and has-a and it is my understanding that the decorator pattern would be most suited for the is-a category - where a Shelby IS-A Mustang.  Thinking of Whipped Cream as a condiment does make the Head First example a bit more confusing.

Given this, in your example of a car with a radio, the radio would be part of the Options collection (or something) and maybe the base car class would have a HasRadio property or something.  But, as pointed out earlier, this is a composition thing (has-a) and not a good candidate for the decorator pattern.

Hope that helps.

 

Michael Hildner replied on Monday, November 06, 2006

I hope I didn't infer that I thought the Head First examples were bad, that was not my intent - I was just pointing out something I don't quite get. I think it's a great, fun book, and have recommended it several times.

The whole is-a has-a thing is what gets me confused. A radio is not a car, but apparently it's an acceptable usage. While I'm not in a position to disagree, it doesn't make sense to me. It does makes sense when you talk about a radio affecting the price, but that's about it. But there are other ways to affect the price too. Not like we have OrderItems inherit from OrderHeaders, nor wrap an AppleOrderItem with an OrangeOrderItem with a MilkOrderItem. They too affect the price.

The examples of ScrollablePanel, Streams, and a Shelby Mustang (I've always loved Shelbys) make perfect sense to me, and are quite clear. A radio being a car and whipped cream being a beverage make no sense to me at all. I'm starting to think that just because something affects the price does not count as adding behaviour.

I appreciate people pointing out the composition thing. That, to me, makes more sense for the car/beverage examples. Especially when I think about my original question - if you have a car decorated by a radio decorated by a sunroof, how can a routine be passed a car, determine if it has a radio, and if it does, turn the radio on? Makes me think that if you need to write code against those concrete types, maybe the decorator pattern is not for you. Exactly what the Head First book says <g>

Many Regards,

Mike

 

SonOfPirate replied on Tuesday, November 07, 2006

The way you would apply the decorator pattern to your example (imo), is to start with a basic car class which is your car with all of the standard features.  We'll call the class Mustang just to continue with my previous analogy.  Of course, this class would inherit from a base Car class.  And, you could break that down even farther into Sportscars, SUVs, Trucks, etc. but that's outside the scope of this discussion - all we care about are the Car and mustang classes.

Traditionally, we think of options and add-ons as singular things, and I think this is where you are getting confused when looking at the Head First book, etc.  Imagine a situation where you couldn't just add-on a sunroof by itself.  The only way to get a sunroof was to "upgrade" the Car to a package that includes a sunroof - say the "Sport" package.  In that case you would have another class called MustangSport which was based on the Mustang class but declared additional properties/attributes for the add-on features that are included in that package - a sunroof being one of them.  Likewise, you'd have a MustangGT class and a MustangShelby class.

What the decorator pattern is trying to accomplish is that your Order object (let's say) would accept a Car object and be able to generate the invoice, receipt and other docs necessary to complete the transaction simply using the base object.  How?  Because the actual instance we are working with is a MustangSport object which possesses all of the knowledge through inheritance to reconstruct itself for the Order object.  What I mean is, the Car class could expose a MSRP property and a ListPrice property.  Each of these changes depending on the "options" you add on.  So, the MustangSport will replace the base Mustang class' implementation of these properties (which already do so to its base Car class) so that the return values reflect the Mustang Sport package.  So, the Order object only needs to call car.ListPrice to get the actual price used for the actual vehicle being sold.

In the same manner, the Car class could expose a Features collection which is added to by the Mustang class and again by the MustangSport class.  This is where the Sunroof would be defined.  So, when the Order object lists what features are included in the purchased vehicle, all it has to do is iterate through the Car object's Features collection.  It doesn't care that items were added by Mustang and MustangSport.

The beauty of this pattern is that your Order object is loosely coupled to your Mustang, MustangSport, MustangGT, etc. classes.  All the Order cares about is the Car class.  So, you can add new models at anytime as long as they are derived from Car.  So, for Ford, when they released the Fusion, your application would simply define a new Fusion class derived from Car and the rest of the code will continue working as before even with the new class.

Where this breaks down in some regards is when you want to have ala cart style add-ons.  Like adding a sunroof to a standard Mustang.  However, with a little creativity, you can accomplish both!!!

Rework the above object model so that each of the add-ons/options is an object of its own.  So, Radio and Sunroof now become objects much in the same way as we did with Mustang, etc.  This allows you to have StandardRadio, PremiumRadio, 5CDRadio, StandardSunroof, PowerSunroof, etc. as objects each representing a different option.  (Of course, you'd want to build in some validation to make sure you didn't add a StandardSunroof AND PowerSunroof to the same vehicle!)

Each of these objects would be derived from a base (abstract) Feature class.  The Feature class exposes Name, Description, MSRP and ListPrice properties that are set by the derived class.

Now, when the Car object calculates and returns the value of the MSRP property, instead of returning a flat value, it iterates the Features collection and totals the MSRP for each feature plus the base MSRP of the Car itself.  Same for ListPrice.

So, when you declare the MustangSport as the purchased item, it adds PowerSunroof and PremiumRadio to the base Car class' Features collection.  Maybe MustangGT adds the 5CD Radio and 5.4L V8 Engine instead of the 4.8L V6 that comes in the base vehicle.  And so on.

This is not necessarily a true implementation of the decorator pattern, I don't believe, because we aren't necessarily using "wrappers" in our design.

Take the example of a coffee order "Dark Roast with Mocha and Whip" - as used in the Head First book.  Dark Roast derives from the base Beverage class.  Mocha and Whip derive from the base Condiment class which in turn inherits Beverage.  In the case of the latter two, they accept a Beverage object in their constructor so that they can serve as wrappers.  So, in the end, even though you are passing a Whip object to your Order, it sees it as a Beverage.  And, because the Whip object wraps a Mocha object which wraps a Dark Roast object, the proper cost can be calculated by walking the chain of wrapped objects.

To follow this same approach using wrappers for our car scenario, you would have a Sport object that would be applied to any Car object - not necessarily realistic.  Likewise, you would have your Radio object derive from Feature (or Option) which in turn inherits Car, accept a Car in its constructor and serve as a wrapper as well.  But remember, that the Radio class does not represent a "radio", it represents a "car with a radio" in this case.  To add a sunroof, you would declare a new Sunroof and pass in the Radio object so that you would have a "car with a radio and sunroof".

I could go on, but hopefully that did the trick.  Good luck.

malloc1024 replied on Tuesday, November 07, 2006

No, you did not infer that the examples in the Head First book were bad.  However, one of the biggest complaints I hear about the book is about the examples.  I think the examples in that book are there to help you learn and remember the patterns.  I don’t think they are necessarily there for best practices.

 

The decorator pattern does have its limitations but it can useful in some situations.  The decorator works fine if you want to change the price of a car by adding options.  However, outside of that use-case, adding options as decorators to a car class doesn’t work as well.  To be able to code against the concrete car class, you would need to keep a reference of the original car object before you decorated it or every decorator would have to implement the whole car class interface.  Both options are less than ideal.  If you are adding options to a car in a use-case that does not involve calculating the purchase price of a car, I would probably just use a CarOptions collection that accepts ICarOption objects.  It would really depend on the situation though.

 

I had the very same questions you had after reading that chapter.  The example in the book can be a little confusing.  IO streams and scrollable panels are better uses of the decorator pattern.

Michael Hildner replied on Tuesday, November 07, 2006

Just want to say thanks to all who spent the time helping me understand this. I really appreciate the explainations. Understanding when to use which pattern is a slow learning curve for me, but it is a lot of fun.

Regards,

Mike

Copyright (c) Marimer LLC