Design patterns in the test of timeDecorator
The decorator pattern is a design pattern that allows behaviour to be added to an existing object dynamically.
I don’t have a lot to say about this pattern.
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 : CondimentDecorator4: {
5: private Beverage m_beverage;6:
7: public Mocha(Beverage beverage)8: {
9: this.m_beverage = beverage;10: }
11:
12: public override String Description13: {
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.
More posts in "Design patterns in the test of time" series:
- (21 Jan 2013) Mediator
- (18 Jan 2013) Iterator
- (17 Jan 2013) Interpreter
- (21 Nov 2012) Command, Redux
- (19 Nov 2012) Command
- (16 Nov 2012) Chain of responsibility
- (15 Nov 2012) Proxy
- (14 Nov 2012) Flyweight
- (09 Nov 2012) Façade
- (07 Nov 2012) Decorator
- (05 Nov 2012) Composite
- (02 Nov 2012) Bridge
- (01 Nov 2012) Adapter
- (31 Oct 2012) Singleton
- (29 Oct 2012) Prototype
- (26 Oct 2012) Factory Method
- (25 Oct 2012) Builder
- (24 Oct 2012) A modern alternative to Abstract Factory–filtered dependencies
- (23 Oct 2012) Abstract Factory
Comments
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.
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.
@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.
ditto re the CachingDecorator. What is wrong with it?
Decorator plays especially nicely with DI containers and cross cutting concerns. StructureMap's Interceptor API just screams decorator at you.
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.
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.
@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: 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.
Comment preview