Introduction
We have all heard about Design Patterns. They're known paths to resolve/improve common development scenarios. Patterns can be modified and extended to fit a wide range of needs.
Here is a variation of the Decorator Pattern using interfaces.
Background
The Decorator Pattern allows to extend the behaviour of a class that cannot be inherited or where the methods are not overridable. Holding a reference to a sealed
class, the decorator class method, when invoked, will execute its code and call the referenced object. The result is an extension of the sealed
class.
Using the code
Supposing we want to build a modular logging library, we can adapt the Decorator Pattern to our needs. One of the nice things is that each decorator class can be used alone or in a pile of other decorators.
Here is the shared interface between all the decorators:
using System;
namespace George.Decorator.ConsoleApp
{
public interface ILoggingProvider
{
string Message {get;set;}
string Source {get;set;}
void Log();
}
}
Now the decorators:
ConsoleDecorator:
using System;
namespace George.Decorator.ConsoleApp
{
public class ConsoleDecorator : ILoggingProvider
{
private ILoggingProvider _iLoggingProvider = null;
private string _message, _source = null;
public ConsoleDecorator(){}
public ConsoleDecorator(ILoggingProvider iLoggingProvider)
{
this._iLoggingProvider = iLoggingProvider;
Message = iLoggingProvider.Message;
Source = iLoggingProvider.Source;
}
public void Log()
{
if(_iLoggingProvider != null)
{
WriteToConsole(_iLoggingProvider.Source,
_iLoggingProvider.Message);
_iLoggingProvider.Log();
}
else
{
WriteToConsole(Source,Message);
}
}
private void WriteToConsole(string source, string message)
{
Console.WriteLine("--------------------------------");
Console.WriteLine(string.Format("Source: {0}",source));
Console.WriteLine(string.Format("Message: {0}",message));
Console.WriteLine("--------------------------------");
Console.Write(Environment.NewLine);
}
public string Message {
get {
return _message;
}
set {
_message = value;
}
}
public string Source {
get {
return _source;
}
set {
_source = value;
}
}
}
}
EventLogDecorator:
using System;
using System.Diagnostics;
namespace George.Decorator.ConsoleApp
{
public class EventLogDecorator: ILoggingProvider
{
private ILoggingProvider _iLoggingProvider = null;
private string _message, _source = null;
public EventLogDecorator(){}
public EventLogDecorator(ILoggingProvider iLoggingProvider)
{
this._iLoggingProvider = iLoggingProvider;
Message = iLoggingProvider.Message;
Source = iLoggingProvider.Source;
}
public void Log()
{
if(_iLoggingProvider!=null)
{
CheckIfSourceExist(_iLoggingProvider.Source);
EventLog.WriteEntry(_iLoggingProvider.Source,
_iLoggingProvider.Message);
_iLoggingProvider.Log();
}
else
{
CheckIfSourceExist(Source);
EventLog.WriteEntry(Source, Message);
}
}
private void CheckIfSourceExist(string source)
{
if(!EventLog.SourceExists(source))
EventLog.CreateEventSource(source, "Application");
}
public string Message {
get {
return _message;
}
set {
_message = value;
}
}
public string Source {
get {
return _source;
}
set {
_source = value;
}
}
}
}
WinformDecorator:
using System;
using System.Windows.Forms;
namespace George.Decorator.ConsoleApp
{
public class WinFormDecorator: ILoggingProvider
{
private ILoggingProvider _iLoggingProvider = null;
private string _message, _source = null;
public WinFormDecorator(){}
public WinFormDecorator(ILoggingProvider iLoggingProvider)
{
this._iLoggingProvider = iLoggingProvider;
Message = iLoggingProvider.Message;
Source = iLoggingProvider.Source;
}
public string Message {
get {
return _message;
}
set {
_message = value;
}
}
public string Source {
get {
return _source;
}
set {
_source = value;
}
}
public void Log()
{
if(_iLoggingProvider != null)
{
ShowMessageBox(_iLoggingProvider.Message,
_iLoggingProvider.Source);
_iLoggingProvider.Log();
}
else
{
ShowMessageBox(Message,Source);
}
}
private void ShowMessageBox(string message, string source)
{
MessageBox.Show(message,source);
}
}
}
We can now use the decorator objects in a standalone mode:
class Program
{
public static void Main(string[] args)
{
ConsoleDecorator consoleDecorator = new ConsoleDecorator();
EventLogDecorator eventLogDecorator = new EventLogDecorator();
WinFormDecorator windowsFormDecorator = new WinFormDecorator();
consoleDecorator.Message = "Hi, here's the console";
consoleDecorator.Source = "Console";
consoleDecorator.Log();
eventLogDecorator.Message = "Hi, here's the event viewer";
eventLogDecorator.Source = "Event viewer";
eventLogDecorator.Log();
windowsFormDecorator.Message = "Hi, here's the windows form";
windowsFormDecorator.Source = "Message Box";
windowsFormDecorator.Log();
Or we can compose a block of logging providers by using the Decorator Pattern:
consoleDecorator = new ConsoleDecorator();
consoleDecorator.Message = "Hi, here's the console again";
consoleDecorator.Source = "Console";
eventLogDecorator = new EventLogDecorator(consoleDecorator);
windowsFormDecorator = new WinFormDecorator(eventLogDecorator);
windowsFormDecorator.Log();
Let's now see why we can only get the benefits by programming using interfaces. In this case, we can use any object implementing the ILoggingProvider
interface to build up the logging, and in other cases, like for instance the use of extensions methods:
using System;
using System.Windows.Forms;
using System.Diagnostics;
namespace George.Decorator.ConsoleApp
{
public static class ConsoleDecoratorExtensions
{
public static void LogWithEventViewer(this ILoggingProvider consoleDecorator)
{
if(!EventLog.SourceExists(consoleDecorator.Source))
EventLog.CreateEventSource(consoleDecorator.Source, "Application");
EventLog.WriteEntry(consoleDecorator.Source,consoleDecorator.Message);
consoleDecorator.Log();
}
public static void LogWithMessageBox(this ConsoleDecorator consoleDecorator)
{
MessageBox.Show(consoleDecorator.Message,consoleDecorator.Source);
consoleDecorator.Log();
}
}
}
The extra value of the interface is that now that we have the extension method available for all the classes implementing it, extension methods targeting classes will be available only to the class.
consoleDecorator.LogWithEventViewer();
consoleDecorator.LogWithMessageBox();
windowsFormDecorator.LogWithEventViewer();
Points of interest
Design Patterns are a fascinating argument, they provide elegant solutions to solve daily problems. In this case, a variation of the Decorator Pattern helps to build up objects that can be independent (usually decorators are used only to extend objects) or related with a kind of inheritance relationship.