Decorator Pattern in C#

In Object Oriented Programming (OOP), the Decorator Pattern is a Design Pattern that allows a client to instantiate a class and then wrap that object with one or more other classes for the purpose of extending the behavior of the original class.

Keep in mind that a “class” is simply a text file that contains code. However, a class becomes an object when the class is instantiated.

With that in mind, consider the fact that once a class is designed and written (and saved to a text file), a client that instantiates that class (to make an object of that class) has no way of changing the inner workings of that class.

The Decorator Pattern is a design construct that allows a client to extend the behavior of the class without having to make changes to the guts of class.

Here’s a design example of how the Decorator Pattern may be implemented in C# .NET.

Program

    1 using System;

    2 using System.Collections.Generic;

    3 

    4 namespace Jedej.Software.Tutorials.DesignPatterns.DecoratorPattern

    5 {

    6     class MainApp

    7     {

    8         /// <summary>

    9         /// This is our tester program

   10         /// </summary>

   11         static void Main()

   12         {

   13             Console.WriteLine("\r\nWe are going to create controller objects and then dynamically add zero or more controller applications to each of the controller objects.\r\n");

   14 

   15             Console.WriteLine("\r\n************************************************************\r\nCreate an Aegis controller…");

   16             var aegis = new Aegis("Aegis Tower 1", "234ASDF20", 343, "ACME Hospital");

   17 

   18             Console.WriteLine("\r\nAdd the Conductivity Sensor application to the Aegis controller that we just created.\r\n");

   19             var c = new ConductivitySensor<Aegis>(aegis);

   20             c.SensorName = "Tower 1 Cond.";

   21             c.TerminalId = "A";

   22             c.Run();

   23 

   24             Console.WriteLine("\r\n************************************************************\r\nCreate a Multiflex controller…");

   25             var multiflex = new Multiflex("Multiflex Boiler 2", "23LJL9-2347A-ASDF", 23048, "Hoover College", "AAAA");

   26 

   27             var bd = new BlowdownControl<Multiflex>(multiflex);

   28             bd.BlowdownValve = 5;

   29             bd.Run();

   30 

   31             var cc = new ConductivitySensor<Multiflex>(multiflex);

   32             cc.SensorName = "Boiler Temperature";

   33             cc.TerminalId = "B";

   34             cc.Run();

   35 

   36             //Pause the console window

   37             Console.ReadLine();

   38         }

   39     }

   40 

   41     /// <summary>

   42     /// The abstract controller class (The Abstract Component)

   43     /// </summary>

   44     abstract class Controller<T>

   45     {

   46         /// <summary>

   47         /// All generic controllers have a Controller ID.

   48         /// </summary>

   49         public static int ControllerId { get; set; }

   50 

   51         /// <summary>

   52         /// All generic controllers have a Name.

   53         /// </summary>

   54         public static string Name { get; set; }

   55 

   56         /// <summary>

   57         /// All generic controllers have a Serial Number

   58         /// </summary>

   59         public static string SerialNumber { get; set; }

   60 

   61         /// <summary>

   62         /// All generic controllers have a method that will execute core controller tasks.

   63         /// Any Decorators that need to Run() will be fired by the concrete decorator class.

   64         /// </summary>

   65         public abstract void Run();

   66     }

   67 

   68     /// <summary>

   69     /// This is a concrete Controller class (The Concrete Component)

   70     /// </summary>

   71     class Aegis : Controller<Aegis>

   72     {

   73         private string systemName = string.Empty;

   74 

   75         // Constructor

   76         public Aegis(string name, string serialNumber, int id, string systemName)

   77         {

   78             Name = name;

   79             SerialNumber = serialNumber;

   80             ControllerId = id;

   81             this.systemName = systemName;

   82         }

   83 

   84         public override void Run()

   85         {

   86             Console.WriteLine("\r\n— Executing Aegis.Run() —\r\n ");

   87             Console.WriteLine(" ID: {0}", ControllerId);

   88             Console.WriteLine(" Name: {0}", Name);

   89             Console.WriteLine(" Serial Number: {0}", SerialNumber);

   90             Console.WriteLine(" System Name: {0}", this.systemName);

   91         }

   92     }

   93 

   94     /// <summary>

   95     /// This is a concrete Controller class (The Concrete Component)

   96     /// </summary>

   97     class Multiflex : Controller<Multiflex>

   98     {

   99         private string systemName = string.Empty;

  100         private string password = string.Empty;

  101 

  102         // Constructor

  103         public Multiflex(string name, string serialNumber, int id, string systemName, string password)

  104         {

  105             Name = name;

  106             SerialNumber = serialNumber;

  107             ControllerId = id;

  108             this.systemName = systemName;

  109             this.password = password;

  110         }

  111 

  112         public override void Run()

  113         {

  114             Console.WriteLine("\r\n— Executing Multiflex.Run() —\r\n ");

  115             Console.WriteLine(" ID: {0}", ControllerId);

  116             Console.WriteLine(" Name: {0}", Name);

  117             Console.WriteLine(" Serial Number: {0}", SerialNumber);

  118             Console.WriteLine(" System Name: {0}", this.systemName);

  119             Console.WriteLine(" Password {0}", this.password);

  120         }

  121     }

  122 

  123     /// <summary>

  124     /// This is the abstract Controller Application class (the abstract Decorator).

  125     /// </summary>

  126     abstract class ControllerApplications<T> : Controller<T>

  127     {

  128         private Controller<T> controller;

  129 

  130         // Constructor

  131         public ControllerApplications(Controller<T> controller)

  132         {

  133             this.controller = controller;

  134         }

  135 

  136         /// <summary>

  137         /// All generic applications will override the generic controller run method.

  138         /// </summary>

  139         public override void Run()

  140         {

  141             this.controller.Run();

  142         }

  143     }

  144 

  145     /// <summary>

  146     /// A Concrete ControllerApplication class (This is a concrete decorator)

  147     /// </summary>

  148     class ConductivitySensor<T> : ControllerApplications<T>

  149     {

  150         private string sensorName = string.Empty;

  151         private string terminalId = string.Empty;

  152 

  153         public string TerminalId

  154         {

  155             get { return terminalId; }

  156             set { terminalId = value; }

  157         }

  158         public string SensorName

  159         {

  160             get { return sensorName; }

  161             set { sensorName = value; }

  162         }

  163 

  164         public ConductivitySensor(Controller<T> controller)

  165             : base(controller)

  166         {

  167 

  168         }

  169 

  170         public override void Run()

  171         {

  172             Console.WriteLine("\r\n— Executing ConductivitySensor.Run —\r\n");

  173             Console.WriteLine("\r\n— ConductivitySensor.Run() will now call base.Run() —\r\n");

  174             base.Run();

  175             Console.WriteLine("\r\nSensor Name: {0}\r\nTerminal ID: {1}", SensorName, TerminalId);

  176         }

  177     }

  178 

  179     /// <summary>

  180     /// A Concrete ControllerApplication class (This is a concrete decorator)

  181     /// </summary>

  182     class BlowdownControl<T> : ControllerApplications<T>

  183     {

  184         private List<Controller<T>> controllersWithBlowdownControl = new List<Controller<T>>();

  185         private int blowdownValve = 0;

  186 

  187         public int BlowdownValve

  188         {

  189             get { return blowdownValve; }

  190             set { blowdownValve = value; }

  191         }

  192 

  193         public BlowdownControl(Controller<T> controller)

  194             : base(controller)

  195         {

  196             this.controllersWithBlowdownControl.Add(controller);

  197         }

  198 

  199         public override void Run()

  200         {

  201             controllersWithBlowdownControl.ForEach(c => Console.WriteLine("\r\n— Executing BlowdownControl.Run —\r\nControllers with blowdown control: {0}\r\n Blowdown Valve: {1}", Name, BlowdownValve));

  202         }

  203     }

  204 }

Program Output

************************************************************
We are going to create controller objects and then dynamically add zero or more
controller applications to each of the controller objects.

************************************************************
Create an Aegis controller…

Add the Conductivity Sensor application to the Aegis controller that we just cre
ated.

— Executing ConductivitySensor.Run —

— ConductivitySensor.Run() will now call base.Run() —

— Executing Aegis.Run() —

ID: 343
Name: Aegis Tower 1
Serial Number: 234ASDF20
System Name: ACME Hospital

Sensor Name: Tower 1 Cond.
Terminal ID: A

************************************************************
Create a Multiflex controller…

— Executing BlowdownControl.Run —
Controllers with blowdown control: Multiflex Boiler 2
Blowdown Valve: 5

— Executing ConductivitySensor.Run —

— ConductivitySensor.Run() will now call base.Run() —

— Executing Multiflex.Run() —

ID: 23048
Name: Multiflex Boiler 2
Serial Number: 23LJL9-2347A-ASDF
System Name: Hoover College
Password AAAA

Sensor Name: Boiler Temperature
Terminal ID: B
************************************************************

Class Diagram of the Program

Considerations

An important design implementation that is worth considering is how the Concrete Decorators (BlowdownControl and ConductivitySensor) HAS-A Controller object and IS-A Controller type. The fact that the decorators HAS-A controller object means that they can manipulate the controller state and the fact that the decorator IS-A Controller type means that they can extend the behavior of the Controller class.

Observer Pattern in the .NET Framework

If you are using the .NET Framework, there is no need to ever implement the Observer Pattern. Instead, you will want to use Delegates and Events.

Per Doug Purdy (Microsoft veteran) and Jeffrey Richter (Wintellect):

…note that no IObserver, IObservable, or ObservableImpl types are present in the Framework. The primary reason for their absence is the fact that the CLR makes them obsolete after a fashion. Although you can certainly use these constructs in a .NET application, the introduction of delegates and events provides a new and powerful means of implementing the Observer pattern without developing specific types dedicated to support this pattern. In fact, as delegates and events are first class members of the CLR, the foundation of this pattern is incorporated into the very core of the .NET Framework. As such, the FCL makes extensive use of the Observer pattern throughout its structure. [ref]

Here’s an example of how to implement the Observer Pattern utilizing .NET Delegates and Events:

    1 //Observer using delegates and events (C#)

    2 

    3 public class Stock

    4 {

    5     //declare a delegate for the event

    6     public delegate void AskPriceDelegate(object aPrice);

    7     //declare the event using the delegate

    8     public event AskPriceDelegate AskPriceChanged;

    9 

   10     //instance variable for ask price

   11     object _askPrice;

   12 

   13     //property for ask price

   14     public object AskPrice

   15     {

   16 

   17         set

   18         {

   19             //set the instance variable

   20             _askPrice = value;

   21 

   22             //fire the event

   23             AskPriceChanged(_askPrice);

   24         }

   25 

   26     }//AskPrice property

   27 

   28 }//Stock class

   29 

   30 //represents the user interface in the application

   31 public class StockDisplay

   32 {

   33 

   34     public void AskPriceChanged(object aPrice)

   35     {

   36         Console.Write("The new ask price is:" + aPrice + "\r\n");

   37     }

   38 

   39 }//StockDispslay class

   40 

   41 public class MainClass

   42 {

   43 

   44     public static void Main()

   45     {

   46 

   47         //create new display and stock instances

   48         StockDisplay stockDisplay = new StockDisplay();

   49         Stock stock = new Stock();

   50 

   51         //create a new delegate instance and bind it

   52         //to the observer’s askpricechanged method

   53         Stock.AskPriceDelegate aDelegate = new

   54            Stock.AskPriceDelegate(stockDisplay.AskPriceChanged);

   55 

   56         //add the delegate to the event

   57         stock.AskPriceChanged += aDelegate;

   58 

   59         //loop 100 times and modify the ask price

   60         for (int looper = 0; looper < 100; looper++)

   61         {

   62             stock.AskPrice = looper;

   63         }

   64 

   65         //remove the delegate from the event

   66         stock.AskPriceChanged -= aDelegate;

   67 

   68     }//Main

   69 

   70 }//MainClass