This blog entry is a response to an onerous blog entry which can be found here : andrewtroelsen.blogspot.com/decorator-pattern-extension-methods.html
The upshot is that the above blog entry wrongly states that you can implement the decorator pattern via .net extension methods, and further goes on to define an implementation which itself is not the decorator pattern.
Many people have commented on the original blog entry, however I don't think any of those comments goes far enough to properly explain why the original posting is wrong, or how it should actually be achieved.
Imagine that we start off with a sealed class.
public sealed class SimpleTextControl
{
public string MyMessage { get; set; }
public void Display() { Console.WriteLine(MyMessage); }
}We cannot simply modify this class definition and say that it implements an interface, because as we have noticed already this class is declared as SEALED. Let us also imagine that we do not have access to the source code.
In order for us to attribute the class with the following extracted interface:
public interface ITextControl
{
string MyMessage { get; set; }
void Display();
}
we first need to use an Adapter:
public class SimpleTextControlAdapter : ITextControl
{
protected readonly SimpleTextControl _AdaptedITextControl = new SimpleTextControl();
public string MyMessage
{
get { return _AdaptedITextControl.MyMessage; }
set { _AdaptedITextControl.MyMessage = value; }
}
public void Display() { _AdaptedITextControl.Display(); }
}
Here, we have extracted an interface (ITextControl) and we have created our adapter as an aggregate which implements that interface by simply handing all calls to an internally cached SimpleTextControl instance.
The Adapter does not add or remove any functionality, it simply implements the newly extracted interface.
Now we can define an abstract Decorator for our Adapter:
public abstract class TextControlDecorator : ITextControl
{
private readonly ITextControl _DecoratedITextControl;
protected TextControlDecorator(ITextControl decorated) { _DecoratedITextControl = decorated; }
public string MyMessage
{
get { return _DecoratedITextControl.MyMessage; }
set { _DecoratedITextControl.MyMessage = value; }
}
virtual public void Display() { _DecoratedITextControl.Display(); }
}
The point behind this step is to remove any repetitious implementation. For example, ITextControl declares public accessors
string MyMessage { get; set; }
which will be handled in exactly the same manner for each decorator implementation.
*NB: This abstract decorator implementation is not part of the Decorator Pattern, and is not always necessary, it only seemed prudent in this example because we have extracted a full and complete interface, which includes the MyMessage accessors.
You should also notice that in our abstract decorator implementation, we have declared the Display member as virtual, which enables us to override that member in all of our concrete decorator classes:
public class BoxedMyTextMessage : TextControlDecorator
{
public BoxedMyTextMessage(ITextControl decorated) : base(decorated) { }
public override void Display()
{
Console.WriteLine("****************************");
base.Display();
Console.WriteLine("****************************");
}
}
and ...
public class TimeStampedMyTextMessage : TextControlDecorator
{
public TimeStampedMyTextMessage(ITextControl decorated) : base(decorated) { }
public override void Display()
{
base.Display();
Console.WriteLine("-> Message sent at {0}", DateTime.Now);
}
}
Now, we have a correct implementation of the Decorator Pattern, and when using our objects, you will notice the composability of our decorators:
class Program
{
static void Main()
{
var control = new SimpleTextControlAdapter { MyMessage = "Testing....1, 2, 3." };
Console.WriteLine("SimpleTextControlAdapter");
control.Display();
Console.WriteLine("\nBoxedMyTextMessage decorated SimpleTextControlAdapter");
new BoxedMyTextMessage(control).Display();
Console.WriteLine("\nTimeStampedMyTextMessage decorated SimpleTextControlAdapter");
new TimeStampedMyTextMessage(control).Display();
Console.WriteLine("\nTimeStampedMyTextMessage decorated BoxedMyTextMessage decorated SimpleTextControlAdapter");
new TimeStampedMyTextMessage(new BoxedMyTextMessage(control)).Display();
Console.WriteLine("\nBoxedMyTextMessage decorated TimeStampedMyTextMessage decorated SimpleTextControlAdapter");
new BoxedMyTextMessage(new TimeStampedMyTextMessage(control)).Display();
Console.ReadLine();
}
}
Running this Program yields the following output in the command window ...
SimpleTextControlAdapter
Testing....1, 2, 3.
BoxedMyTextMessage decorated SimpleTextControlAdapter
****************************
Testing....1, 2, 3.
****************************
TimeStampedMyTextMessage decorated SimpleTextControlAdapter
Testing....1, 2, 3.
-> Message sent at 26/08/2009 20:25:53
TimeStampedMyTextMessage decorated BoxedMyTextMessage decorated SimpleTextControlAdapter
****************************
Testing....1, 2, 3.
****************************
-> Message sent at 26/08/2009 20:25:53
BoxedMyTextMessage decorated TimeStampedMyTextMessage decorated SimpleTextControlAdapter
****************************
Testing....1, 2, 3.
-> Message sent at 26/08/2009 20:25:53
****************************
NB: It is very common to find that patterns are not found in isolation. It is also very common to find that you have been using design patterns without actually knowing that you have!
NOTE:
It has also been mentioned in comments about the original posting that an IoC container can be used to instantiate our decorated object, in which case the code only neccessarily needs to deal with the ITextControl interface. Again, however, the use of IoC containers is not a part of the Decorator Pattern itself, but is another strategy and pattern in its own right.
No comments:
Post a Comment