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.