Wednesday, April 23, 2008

Decorator Pattern

The pattern states: Attach additional responsibilities to an object dynamically. The Decorator Pattern is used for adding additional functionality to a particular object as opposed to a class of objects. It is easy to add functionality to an entire class of objects by subclassing an object, but it is impossible to extend a single object this way. With the Decorator Pattern, you can add functionality to a single object and leave others like it unmodified. Below is an example of a drawing program that can draw a square:

Interface: IShape
1
2
3
4
5
6
7
namespace Decorator
{
    public interface IShape
    {
        void Draw();
    }
}
Class: Square : IShape
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace Decorator
{
    public class Square : IShape
    {
        private readonly int _Size;

        public Square(int size)
        {
            _Size = size;
        }

        public void Draw()
        {
            Console.WriteLine("Draw square, size:" + _Size);
        }
    }
}
In the next version we want to be able to add a shadow to the square, fill the square with color or do both. We could extend the Square class three times with the individual behavior changes, or we might want to extend it once with all the new behavior and make it optional, but in that case the client needs to know about these options and that will break the Open Closed Principle. So here comes the Decorator Pattern.
Class: Decorator : IShape
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace Decorator
{
    public class Decorator : IShape
    {
        private readonly IShape _Shape;

        public Decorator(IShape shape)
        {
            _Shape = shape;
        }

        public virtual void Draw()
        {
            _Shape.Draw();
        }
    }
}
Class: AddFill : Decorator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace Decorator
{
    public class AddFill : Decorator
    {
        public AddFill(IShape shape) : base(shape)
        {
        }

        public override void Draw()
        {
            base.Draw();
            Console.WriteLine("Add fill to IShape object");
        }
    }
}
Class: AddShadow : Decorator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace Decorator
{
    public class AddShadow : Decorator
    {
        public AddShadow(IShape shape) : base(shape)
        {
        }

        public override void Draw()
        {
            Console.WriteLine("Add shadow to IShape object");
            base.Draw();
        }
    }
}
When you look at the two decoration classes above you see that the AddFill implementation of Draw first calls the base.Draw method while the AddShadow implementation first add its custom addon and than calls the base.Draw method. Now look at how we can implement these Decoration classes:
Class: Main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace Decorator
{
    class Program
    {
        static void Main(string[] args)
        {
            IShape squareShape = new Square(100);
            squareShape.Draw();
            Console.WriteLine();

            IShape addFill = new AddFill(squareShape);
            addFill.Draw();
            Console.WriteLine();

            IShape addShadow = new AddShadow(addFill);
            addShadow.Draw();

            Console.ReadKey();
        }
    }
}
Output
1
2
3
4
5
6
7
8
Draw square, size:100

Draw square, size:100
Add fill to IShape object

Add shadow to IShape object
Draw square, size:100
Add fill to IShape object
I hope you found these examples helpful. Also take a look at the following links:

http://www.dofactory.com/Patterns/PatternDecorator.aspx
http://www.patnys.com/archive/2008/04/21/decorator-pattern.aspx

No comments: