The Decorator patterns allows an objects behavior to be dynamically altered at runtime.
This change in behavior is accomplished by wrapping an object with a new object. This new wrapper object is the Decorator. The Decorator object will implement the same interface as the object being wrapped. Because the decorated object uses the same interface as an undecorated object, other parts of your system do not need to be aware of whether or not one or more decorations are in use.
The decorator (wrapper) can now alter the behavior of the object being wrapped by either completely replacing various methods or by altering the behavior of the methods of the object being decorated.
Example
A simple example of a Decorator can be demonstrated with a report class. Some reports may need a header and some may also need a footer. Still others might require both a header and a footer.
First, since the Decorator needs to implement the same interface as the object being decorated, let's define an Interface.
Public Interface IReport
Sub Print()
End Interface
Now we can create a very simple report class that implements that interface.
Public Class Report
Implements IReport
Public Sub Print() Implements IReport.Print
HttpContext.Current.Response.Write("This is the report body.")
End Sub
End Class
Next we can create a Decorator class to add a header to our report. Since our Decorator will wrap around our report, it requires that a report instance is passed to its constructor.
Public Class ReportHeaderDecorator
Implements IReport
Private _innerReport As IReport
Public Sub New(ByVal innerReport As IReport)
'save a reference to the object being decorated
_innerReport = innerReport
End Sub
Public Sub Print() Implements IReport.Print
'add header decoration first
HttpContext.Current.Response.Write("<h3>This is the report header</h3>")
HttpContext.Current.Response.Write("<hr />")
'now let the report being decorated do its printing
_innerReport.Print()
End Sub
End Class
We can also create another Decorator to add a footer to our report.
Public Class ReportFooterDecorator
Implements IReport
'save a reference to the object being decorated
Private _innerReport As IReport
Public Sub New(ByVal innerReport As IReport)
_innerReport = innerReport
End Sub
Public Sub Print() Implements IReport.Print
'let the report being decorated do its printing first
_innerReport.Print()
'now we add the footer decoration
HttpContext.Current.Response.Write("<hr />")
HttpContext.Current.Response.Write("<h6>This is the report footer.</h6>")
End Sub
Since the Report class and the decorators all implement the same IReport Interface, regular reports and decorated reports can be used anywhere that an IReport is accepted. Here is how we can create a report and then wrap it with the decorators.
Dim myReport As IReport
myReport = New Report 'create the basic report
myReport = New ReportHeaderDecorator(myReport) 'wrap with a header decoration
myReport = New ReportFooterDecorator(myReport) 'wrap with a footer decoration
myReport.Print()
The result of printing this report will look something like this:
This is the report header
This is the report body.
This is the report footer.
A similar effect could by achieved through sub-classing. This would require 3 subclasses of our Report class. A ReportWithHeader subclass, a ReportWithFooter subclass and also a ReportWithHeaderAndFooter subclass. As more and more decorations are needed, it quickly becomes unmanageable to create all the needed subclasses to cover all the possible combinations.
Since the various Decorator classes can be combined at runtime, we do not have to predefine all the possible combinations.
For C#, the sample code would look like this:
public interface IReport
{
void Print();
}
public class Report : IReport
{
public void IReport.Print()
{
HttpContext.Current.Response.Write("This is the report body.");
}
}
public class ReportHeaderDecorator : IReport
{
private IReport _innerReport;
public ReportHeaderDecorator(IReport innerReport)
{
//save a reference to the object being decorated
_innerReport = innerReport;
}
public void IReport.Print()
{
//add header decoration first
HttpContext.Current.Response.Write("<h3>This is the report header</h3>");
HttpContext.Current.Response.Write("<hr />");
//now let the report being decorated do its printing
_innerReport.Print();
}
}
public class ReportFooterDecorator : IReport
{
//save a reference to the object being decorated
private IReport _innerReport;
public ReportFooterDecorator(IReport innerReport)
{
_innerReport = innerReport;
}
public void IReport.Print()
{
//let the report being decorated do its printing first
_innerReport.Print();
//now we add the footer decoration
HttpContext.Current.Response.Write("<hr />");
HttpContext.Current.Response.Write("<h6>This is the report footer.</h6>");
}
}
//implementing the decorator
IReport myReport;
myReport = new Report();
//create the basic report
myReport = new ReportHeaderDecorator(myReport);
//wrap with a header decoration
myReport = new ReportFooterDecorator(myReport);
//wrap with a footer decoration
myReport.Print();