I am here with an example oriented design pattern. The design pattern is Decorator.
According to Gof: "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub classing for extending functionality"
Closed for Modification and Open for Extension
One of the main challenges we face in development is Change. The Closed for Modification and Open for Extension principle says a new functionality can be added by keeping the original code unchanged.
Example:
You have an Album class today which is just blank now. Tomorrow if the customer wants a Christmas tree on that.. What would be our approach?
We will modify the original Album class to incorporate the Christmas tree on it. This should not be the best approach. There is a better approach to this. We can still keep the Album class unchanged and add the Christmas tree to it. Everything happens in the runtimeā¦ that is the cool part of it.
Some more examples
We can see real life controls like Form, Button etc. There would be a Form class with built-in functionality. Still the user can use it and add new controls to it / extend the functionality. Here we will be basically deriving from the existing Form/Button class and add new methods or properties to it.
The difference between the above approach and Decorator pattern is that, in the Decorator pattern, it is done during runtime.
Conclusion on Change
So basically we can conclude that whenever changes are required, the possible solutions could be:
- Change the original class
- Subclass it and create instance of subclass
- Use Decorator Pattern and still using the original class instance
Here we are going to see how we can use Decorator Pattern to help with the following scenario.
Requirement
The requirement here would be to provide a default Album object and based on dynamic requirement from the user in runtime, we have to draw other pictures to the album.
Design
Our first class would be the Album class which has a Graphics object as parameter.
It contains a Draw() method which is virtual and just clears the graphics object.
public class Album
{
public Graphics Graphics
{
get;
set;
}
public Album()
{
}
public Album(Graphics graphics)
{
Graphics = graphics;
}
public virtual void Draw()
{
Graphics.Clear(Color.White);
}
}
Decorator
We are adding the class named AlbumDecorator which will serve as the base class for all decorators.
public abstract class AlbumDecorator : Album
{
protected Album _album;
public AlbumDecorator(Album album)
{
_album = album;
}
public override void Draw()
{
_album.Draw();
}
}
It takes an Album class as parameter in the constructor.
There are ChristmasTreeDecorator, SantaClausDecorator, StarDecorator deriving from AlbumDecorator:
public class ChristmasTreeDecorator : AlbumDecorator
public class SantaClausDecorator : AlbumDecorator
public class StarDecorator : AlbumDecorator
Each class deriving from AlbumDecorator has it's own picture to draw.
Invoking
In the main form we create an instance of Album class and assign it to form field _album.
private Album _album;
_album = new Album(_graphics);
_album.Draw();
In the runtime, when user wants a Christmas Tree, an instance of ChristmasTreeDecorator is created.
_album = new ChristmasTreeDecorator(_album);
_album.Draw();
In the above code we can see the same _album.Draw() method is called.
How it works
Whenever we call the Draw() method of a decorator class, it in turns calls the original Album.Draw(). After that it will call it's own Draw() method. In this way we can pass the same album instance to multiple decorators. If there are 10 decorators, all the decorator Draw() methods will be invoked.
You can test this by placing a breakpoint inside the StarDecorator Draw() method.
Note
Using decorator we can add dynamic responsibilities to an object in runtime. This provides us the flexibility of creating an instance of decorators on an as-needed basis. This would provide a real advantage in scenarios where the additional responsibility increases the use of memory.