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.