Decorator
Quick Information/Overview
Pattern Type | Structural |
Applicable Language/Framework | Agnostic OOP |
Pattern Source | Gang of Four |
Difficulty | Easy |
Up Front Definitions
- Decorator: An object that is both an inherits from and operates on another object.
- Component: Abstract target base class (or interface) of the decorator pattern.
- Target: For the purposes of this post, I use this term interchangeably with Component.
The Problem
Let’s say that you have to model employees at a company, and all employees have a name, an hourly wage, and a bio that describes them. Furthermore, these items are going to be determined by the type of employee that each employee is (they can be Workers, Managers, or Executives for now, but there might be more later). Since this begs for an extensible, polymorphic structure, you create the following class structure and program to take it for a test drive:
public abstract class Employee
{
public string Name { get; protected set; }
public double HourlyWage { get; protected set; }
public string EmployeeBio { get; protected set; }
protected Employee(string name, double hourlyWage, string bio)
{
HourlyWage = hourlyWage;
Name = name;
EmployeeBio = bio;
}
}
public class Worker : Employee
{
public Worker(string name) : base(name, 12.5, "Rank and File") { }
}
public class Manager : Employee
{
public Manager(string name) : base(name, 25.0, "Line Manager") { }
}
public class Executive : Employee
{
public Executive(string name) : base(name, 50.0, "Bigwig") { }
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert"));
myEmployees.Add(new Manager("Michael Scott"));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1}, makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Simple enough. This is nice and clean because later, if we want to add a new type of employee (maybe Toby from HR), we can just create a new type of employee through inheritance, and the print function will hum happily along. This is classic Open/Closed Principle stuff. Different types of employees define their own attributes, and there are no ugly enums called “EmployeeType” — life is good.
Now, let’s say that an industrious project manager gets wind of the rave reviews your office modeling program has gotten for its usability and clean design and decides to hitch his wagon to your effort. You get a request coming in that employees be able to be distinguished as “high performers”. Any employee with the “high performer” designation should be paid 10% more than non-high performer counterparts, and this recognition should be reflected in the employee’s bio. So, you change the code to look like this:
public abstract class Employee
{
public string Name { get; protected set; }
public double HourlyWage { get; protected set; }
public string EmployeeBio { get; protected set; }
protected Employee(string name, double hourlyWage, string bio, bool isHighPerformer = false)
{
HourlyWage = hourlyWage;
Name = name;
EmployeeBio = bio;
if (isHighPerformer)
{
HourlyWage *= 1.10;
EmployeeBio = EmployeeBio + ", High Performer";
}
}
}
public class Worker : Employee
{
public Worker(string name, bool isHighPerformer = false) : base(name, 12.5, "Rank and File", isHighPerformer) { }
}
public class Manager : Employee
{
public Manager(string name, bool isHighPerformer = false) : base(name, 25.0, "Line Manager", isHighPerformer) { }
}
public class Executive : Employee
{
public Executive(string name, bool isHighPerformer = false) : base(name, 50.0, "Bigwig", isHighPerformer) { }
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert", true));
myEmployees.Add(new Manager("Michael Scott"));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1}, makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Hmmm… polymorphic structure still looks good, and the optional parameter even allows you to implement this functionality as a non-breaking change for client code, but the constructors are starting to get a little noisy and unfocused. It’s also not necessarily awesome that you had to touch every class constructor in the polymorphic structure, but whatever, project management is happy and hopefully they don’t ask for more stuff like that.
And, turns out they don’t. Instead, they want to be able to record the fact that some employees are members of the corporate “safety team”, responsible for knowing how fire extinguishers work and counting people during fire drills and whatnot. So, during the normal print, safety team membership should be reflected in the bio, but it should also be possible to see what a safety team member’s team responsibility is.
Here we have a classic inheritance situation… sort of. Safety team members are still normal employees (meaning any safety team member must be worker, manager or exec), so they can’t inherit from the abstract base employee class. And, it doesn’t make sense to add safety team information and implementation to any existing classes, since none of them necessarily represent safety team members. So, you suppose they’ll just have to inherit from the three classes we’ve defined so far, like this:
public abstract class Employee
{
public string Name { get; protected set; }
public double HourlyWage { get; protected set; }
public string EmployeeBio { get; protected set; }
protected Employee(string name, double hourlyWage, string bio, bool isHighPerformer = false)
{
HourlyWage = hourlyWage;
Name = name;
EmployeeBio = bio;
if (isHighPerformer)
{
HourlyWage *= 1.10;
EmployeeBio = EmployeeBio + ", High Performer";
}
}
}
public class Worker : Employee
{
public Worker(string name, bool isHighPerformer = false) : base(name, 12.5, "Rank and File", isHighPerformer) { }
}
public class SafetyTeamWorker : Worker
{
public string SafetyDescription { get; set; }
public SafetyTeamWorker(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
public class Manager : Employee
{
public Manager(string name, bool isHighPerformer = false) : base(name, 25.0, "Line Manager", isHighPerformer) { }
}
public class SafetyTeamManager : Manager
{
public string SafetyDescription { get; set; }
public SafetyTeamManager(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
public class Executive : Employee
{
public Executive(string name, bool isHighPerformer = false) : base(name, 50.0, "Bigwig", isHighPerformer) { }
}
public class SafetyTeamExecutive : Executive
{
public string SafetyDescription { get; set; }
public SafetyTeamExecutive(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert", true));
myEmployees.Add(new SafetyTeamManager("Michael Scott"));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1}, makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Uh, oh. This is starting to smell. You’re not thrilled about creating three classes when what you really wanted was one class, and you’re really not thrilled about the fact that the three classes are copy and paste jobs of one another. But, no time for all that clean code claptrap now, you’re getting more requirements in from the project manager and you’ve got to get things done. They system also has to be able to track whether employees are on the softball team and, if so, what position they play. So that means, another three copy and paste classes. Except, wait a minute, safety team members can be softball players too. So, we really need to add another 6 copy and paste classes, bringing the total to 9 copies and 3 that we started with:
public abstract class Employee
{
public string Name { get; protected set; }
public double HourlyWage { get; protected set; }
public string EmployeeBio { get; protected set; }
protected Employee(string name, double hourlyWage, string bio, bool isHighPerformer = false)
{
HourlyWage = hourlyWage;
Name = name;
EmployeeBio = bio;
if (isHighPerformer)
{
HourlyWage *= 1.10;
EmployeeBio = EmployeeBio + ", High Performer";
}
}
}
public class Worker : Employee
{
public Worker(string name, bool isHighPerformer = false) : base(name, 12.5, "Rank and File", isHighPerformer) { }
}
public class SoftballPlayingWorker : Worker
{
public string Position { get; set; }
public SoftballPlayingWorker(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
public class SafetyTeamWorker : Worker
{
public string SafetyDescription { get; set; }
public SafetyTeamWorker(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
public class SoftballPlayingSafetyTeamWorker : SafetyTeamWorker
{
public string Position { get; set; }
public SoftballPlayingSafetyTeamWorker(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
public class Manager : Employee
{
public Manager(string name, bool isHighPerformer = false) : base(name, 25.0, "Line Manager", isHighPerformer) { }
}
public class SoftballPlayingManager : Manager
{
public string Position { get; set; }
public SoftballPlayingManager(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
public class SafetyTeamManager : Manager
{
public string SafetyDescription { get; set; }
public SafetyTeamManager(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
public class SoftballPlayingSafetyTeamManager : SafetyTeamManager
{
public string Position { get; set; }
public SoftballPlayingSafetyTeamManager(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
public class Executive : Employee
{
public Executive(string name, bool isHighPerformer = false) : base(name, 50.0, "Bigwig", isHighPerformer) { }
}
public class SoftballPlayingExecutive : Executive
{
public string Position { get; set; }
public SoftballPlayingExecutive(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
public class SafetyTeamExecutive : Executive
{
public string SafetyDescription { get; set; }
public SafetyTeamExecutive(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Safety Team Member";
}
}
public class SoftballPlayingSafetyTeamExecutive : SafetyTeamExecutive
{
public string Position { get; set; }
public SoftballPlayingSafetyTeamExecutive(string name, bool isHighPerformer = false) : base(name, isHighPerformer)
{
EmployeeBio += ", Softball Player";
}
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert", true));
myEmployees.Add(new SafetyTeamManager("Michael Scott"));
myEmployees.Add(new SoftballPlayingSafetyTeamExecutive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1}, makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Suddenly, you’ve got a train-wreck on your hands. After you track down a few hard-to-fix defects that resulted from the wrong inheritance scheme when you forgot to change “Worker” to “Manager” in one of your copy-paste implementations, the project manager tells you that there’s no time for refactoring because now you need to be able to who who is on the Party Planning Committee and who is a member of the Finer Things Club. Your 12 classes just became 36 and then 108.
The only option at this point is clearly to write a program that can automate generating this code for you. That, or retire.
So, What to Do?
As is often the case in this series of posts, the important thing is to retrace our steps and figure out where things went off the rails. And, as is even more often the case, our problems started as soon as we started duplicating code, and they got worse the more egregiously we did it. So, where do we go from here? It’s simpler in this post than in some of the others — let’s start by deleting all of these misguided inheritors and revert to the state we were in immediately after defining our optional “isHighEarner” parameter (we’ll get to that later).
Now, let’s add a class called “EmployeDecorator”. It will inherit from Employee and be abstract. If you’ll recall, when implementing the safety team requirement, we dismissed the possibility of safety team inheriting from Employee directly because safety team members also needed to be Workers, Managers, or Executives, but we’re going to introduce a way around that problem:
public abstract class EmployeeDecorator : Employee
{
private readonly Employee _target;
protected Employee Target { get { return _target; } }
public EmployeeDecorator(Employee target)
{
_target = target;
Name = target.Name;
HourlyWage = target.HourlyWage;
EmployeeBio = EmployeeBio;
}
}
Notice that this decorator object curiously both inherits from and wraps Employee, and proceeds to seed its own properties with those of the employee. This allows the decorator to look to clients as though it is the object its wrapping. This has some powerful ramifications, as we can see here:
public class SafetyTeamMember : EmployeeDecorator
{
public string SafetyDescription { get; set; }
public SafetyTeamMember(Employee target) : base(target)
{
EmployeeBio += "Safety Team Member ";
}
}
Now, we can add safety team member functionality to an employee simply by taking an employee and injecting it into the constructor of the concrete decorator. As we do this, we trust the decorator to apply the appropriate modifications to the employee and expose the properties that we would demand of the employee. So, in this case decorator adds bio information and when we query its bio, we get the employee’s information plus the “decoration” of the addition to the bio. On top of that, we can set this object’s “Safety Description”. In effect, we’re extending the functionality of an object without inheriting directly from it. The common ancestor is, however necessary, as this is what gives us access to the protected members of employee. Here is the client instantiation logic:
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert", true));
var myManager = new Manager("Michael Scott");
myEmployees.Add(new SafetyTeamMember(myManager));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
And this results in the same printed information. Now, let’s implement our softball player requirement and set Michael Scott up as both a softball player and a safety team member.
public class SoftballPlayer : EmployeeDecorator
{
public string Position { get; set; }
public SoftballPlayer(Employee target) : base(target)
{
EmployeeBio += "Softball Player, ";
}
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
myEmployees.Add(new Worker("Jim Halpert", true));
Employee myManager = new Manager("Michael Scott");
myManager = new SoftballPlayer(myManager);
myEmployees.Add(new SafetyTeamMember(myManager));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1} makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Now, Michael is a real Rennaisance Man, and it only took 3 extra classes rather than 12. And, that number will raise to 5, rather than 108, with the next set of project management requirements. So, we’re setting pretty, but we can do even better — we can refactor the higher performer functionality to be part of another decorator as well, to achieve this finished product:
public abstract class Employee
{
public string Name { get; protected set; }
public double HourlyWage { get; protected set; }
public string EmployeeBio { get; protected set; }
protected Employee() { }
protected Employee(string name, double hourlyWage, string bio)
{
HourlyWage = hourlyWage;
Name = name;
EmployeeBio = bio;
}
}
public class Worker : Employee
{
public Worker(string name) : base(name, 12.5, "Rank and File, ") { }
}
public class Manager : Employee
{
public Manager(string name) : base(name, 25.0, "Line Manager, ") { }
}
public class Executive : Employee
{
public Executive(string name) : base(name, 50.0, "Bigwig, ") { }
}
public abstract class EmployeeDecorator : Employee
{
private readonly Employee _target;
protected Employee Target { get { return _target; } }
public EmployeeDecorator(Employee target)
{
_target = target;
Name = target.Name;
HourlyWage = target.HourlyWage;
EmployeeBio = target.EmployeeBio;
}
}
public class SafetyTeamMember : EmployeeDecorator
{
public string SafetyDescription { get; set; }
public SafetyTeamMember(Employee target) : base(target)
{
EmployeeBio += "Safety Team Member, ";
}
}
public class SoftballPlayer : EmployeeDecorator
{
public string Position { get; set; }
public SoftballPlayer(Employee target) : base(target)
{
EmployeeBio += "Softball Player, ";
}
}
public class HighPerformer : EmployeeDecorator
{
public HighPerformer(Employee target) : base(target)
{
EmployeeBio += " High Performer,";
HourlyWage *= 1.10;
}
}
class Program
{
static void Main(string[] args)
{
var myEmployees = new List();
Employee myWorker = new Worker("Jim Halpert");
myEmployees.Add(new HighPerformer(myWorker));
Employee myManager = new Manager("Michael Scott");
myManager = new SoftballPlayer(myManager);
myEmployees.Add(new SafetyTeamMember(myManager));
myEmployees.Add(new Executive("Jan Levenson"));
PrintAllEmployees(myEmployees);
}
private static void PrintAllEmployees(ListmyEmployees)
{
foreach (var myEmployee in myEmployees)
{
Console.WriteLine(String.Format("{0}, {1} makes {2} per hour.", myEmployee.Name, myEmployee.EmployeeBio, myEmployee.HourlyWage));
}
Console.ReadLine();
}
}
Now, that weird extra parameter is gone — “high performance” is now something added to employees rather than ingrained. And, the high performance decorator illustrates nicely that decorators can serve either or both of two purposes: extending functionality and/or modifying the target (“component”).
A More Official Explanation
Turning once again to dofactory (UML diagram image came from here as well), a more formal definition of the Decorator Pattern is to:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
The key thing to note in our process or refactoring toward the Decorator pattern is that we were defining a scheme whereby employees could be given ancillary responsibilities without changing their fundamental makeup. In our example, all employees have a wage, name, and bio, but not all employees are safety team members, high earners, etc. The Decorator pattern allows us to reflect this reality by attaching these ancillary responsibilities in ad hoc fashion, and without adding any properties to the Employee class or sub-classes that may not be used.
The Decorator Pattern is a means for extending a class’s functionality without either modifying that class or inheriting from that class. It takes advantage of the nature of inheritance to gain access to the class’s protected members in order to expose new state modifying behavior to its clients.
Other Quick Examples
Here are some other places that the Decorator pattern is used:
- GUI scenarios where a GUI component may have many tangentially related properties (e.g. a window may have scrollbars, which are not really intrinsic to the Window itself, but handy to be able to add)
- “Stream” objects (Java) that add behavior to application I/O by decorating the basic I/O implementation.
- Anywhere that there is a significant desire to extend the main functionality of an object hierarchy, such as a series of customizations to an invoice or order object.
A Good Fit – When to Use
The Decorator pattern makes sense to use if you have an object hierarchy that you want to keep pretty much as-is, but you want to add some custom, kind of one-off behavior to it. In the example above, we like the employee hierarchy and don’t really want to clutter it up, but we would like to capture the fact that employees can also occasionally have other, custom behaviors. This makes senses if we extend the example to the GUI components like windows as well. We may sometimes want Windows to have scrollbars, but that doesn’t mean there is a need to have ScrollbarWindow, HorizontalScrollbarWindow, VerticalScrollbarWindow, BothScrollbarWindow, etc for every conceivable type of window.
In fact, that type of combinatorial explosion of types is another definite call for the Decorator pattern. The pattern is a good fit for adding one-off behavior to object hierarchies, but it’s pretty much a must if you find yourself in a situation where simple inheritance would mean defining something like n! new classes for each behavior you want. If you find yourself saying “well, I’d need a HorizontalScrollbarDialogWindow and a HorizontalScrollbarNormalWindow, and a VerticalScrollbarDialogWindow, etc”, stop and give serious though to Decorator.
Square Peg, Round Hole – When Not to Use
This pattern may not be necessary for certain scenarios. Consider our working example above, and forget all of the other “hats” employees can wear but the Safety Team. If we knew this was the only other thing to be modeled, it might be worth having a SafetyTeam object that stored a collection of employee objects. This would allow all members of the safety team to be identified. Granted, you wouldn’t get it as part of the employee bio, but perhaps that could be achieved in another way. Point is, Decorator may be overkill if there isn’t much decoration going on.
Decorator also may not fit if you have no combinatorial explosion and significant control over and flexibility with the object hierarchy. It’s not an option that I personally like, and I think it’s a bit clumsy, but we could have achieved some of the functionality above with the Employee base class having properties for IsSafetyTeamMember and IsHighPerformer and IsSoftballPlayer. If we knew that we were limited to these three (and thus not building an outhouse), this rigid design would suffice.
So, the point is that having and dealing with some existing object hierarchy does not automatically mean that the Decorator pattern is a good fit. It introduces some design complexity and potential understanding issues, so it should be introduced to solve a real, current problem, rather than a hypothetical future one.
So What? Why is this Better?
As demonstrated above, Decorator makes code a lot cleaner in situations where responsibilities need to be added in ad-hoc fashion to an existing object hierarchy. This is particularly true when the new responsibilities are somewhat tangential to the main hierarchy. Using this pattern allows you to manipulate protected members of the class in question without sub-classing it directly, and to present an object to clients that looks uniform and hides the trickery that you’ve pulled.
Your inheritance hierarchies when using this pattern will remain both shallower and narrower, which definitely facilitates better understanding of code. But, you won’t have to sacrifice any flexibility to get this more manageable inheritance tree. And, in the end, you’ll save yourself the ugliness of getting into a situation where the number of classes you have are described in terms of factorial.
Great article, thanks.
Hi Robert — thanks for reading, and I’m glad you enjoyed.