Ayende @ Rahien

Refunds available at head office

Design patterns in the test of time: Decorator

The decorator pattern is a design pattern that allows behaviour to be added to an existing object dynamically.

More about this pattern.

I don’t have a lot to say about this pattern.

Hipster Ariel - Decorator? Used it before it was cool

The decorator pattern is still just as useful if not more so as the time it was initially announced. It forms a core part of many critical function of your day to day software.

The most obvious example is the notion of Stream, where you often decorate a stream (Buffered Stream, Compressing Stream, etc). This example is valid not just for the .NET framework, I can’t think of a single major framework that doesn’t use the Stream abstraction for its IO.

Then again, the cautionary side, people try to use it in… strange ways:

   1: public abstract class CondimentDecorator : Beverage {}
   2:  
   3: public class Mocha : CondimentDecorator
   4: {
   5:     private Beverage m_beverage;
   6:  
   7:     public Mocha(Beverage beverage)
   8:     {
   9:         this.m_beverage = beverage;
  10:     }
  11:  
  12:     public override String Description
  13:     {
  14:         get
  15:         {
  16:             return m_beverage.Description + ", Mocha";
  17:         }
  18:     }
  19:  
  20:     public override double Cost()               
  21:     {                                      
  22:         return 0.20 + m_beverage.Cost();
  23:     }
  24: }

Something like this, for example, is a pretty bad way of doing things. It assumes a very fixed model of looking at the universe, which is just not true. Decorator works best when you have just a single I/O channel. When you have multiple inputs & outputs, decorating something becomes much harder.

In particular, implementing business logic like the one above in decorators make it very hard to figure out why things are happening. In particular, CachingDecorator is something that you want to avoid (better to use infrastructure or a auto caching proxy, instead).

Recommendation: It is a very useful pattern, and should be used when you actually have one input / output channel, because that is a great way to allow to dynamically compose the way we apply processing to it.

Comments

Jason Young
11/07/2012 01:03 PM by
Jason Young

Is that code example from Head First Design Patterns? I found that book's examples to not always be very motivating--that is, they demonstrated use of a pattern, but not necessarily a case where you'd actually want to use that pattern.

Michal Lašák
11/07/2012 04:02 PM by
Michal Lašák

Could you please specify more, why we should avoid CachingDecorator? Actually I thought that it was very elegant solution to do caching and work with DI container.

Euphoric
11/07/2012 07:58 PM by
Euphoric

@Michal : I think one reasons are side effects. This decorator would need to know if underlying data/decorated object changed and invalidate itself. This introduces hard to debug problems when cache decorator returns invalid data. I think this is what having one input/output channel means.

Jacob T
11/07/2012 07:58 PM by
Jacob T

ditto re the CachingDecorator. What is wrong with it?

Chris
11/07/2012 09:29 PM by
Chris

Decorator plays especially nicely with DI containers and cross cutting concerns. StructureMap's Interceptor API just screams decorator at you.

Ayende Rahien
11/07/2012 11:49 PM by
Ayende Rahien

Euphoric, That is certainly one aspect of that. The other aspect is that when you add a new method to the decorated object, you now need to go and add that to all the decorating, etc. I don't like this sort of behavior.

Stan
11/08/2012 06:38 AM by
Stan

Decorator can be usefull in c++ with its multiple inheritance. For c# it's mostly useless for reasons Ayende have provided. Something like dynamic proxy or post sharping may be better used for injecting behaviour.

Frisian
11/08/2012 08:25 AM by
Frisian

@Ayende: That's exactly one of the differences between Proxy and Decorator: In a decorator behaviour is added with the API of the underlying object in mind. So if that API changes, the decorator has to be modified as well. In a proxy on the other hand, you have a one-size-fits-all approach, i.e. adding the same functionality to every method of the underlying object. Consequently, proxies can be constructed dynamically in many languages, usually employing reflection.

Stan
11/08/2012 06:03 PM by
Stan

Stan: I'm not sure how dynamic proxy or using PostSharp is different. OK, you don't write decorator by hand but still something is decorating object. Runtime behaviour is same.

Comments have been closed on this topic.