Extending Object Behavior with the Decorator Pattern
October 13, 2018
In a previous article, we discussed how to use the strategy pattern to dynamically change an object’s behavior at runtime. Classically, polymorphism in object-oriented design is static and achieved through inheritance; however, with the strategy pattern you can accomplish the same goal dynamically. Indeed, this is an excellent way to handle situations when you need an object to exhibit different behavior at different times. However, it’s worth noting that the strategy pattern requires mutation of the object you’re working with. By using the strategy pattern, you are necessarily changing the algorithm that an object uses for a given behavior. In some situations, it may be preferable not to mutate a given object. Or more likely, you won’t even have the option of mutating an object because it may come from a codebase over which you have no control (such as an external library). Such cases are relatively common; however, it’s still possible to enhance an immutable object’s behavior. One effective means to do so is with the decorator pattern.
Why Decorate?
The decorator pattern, also known as the wrapper pattern, is when you wrap an object within another object, thus providing a means of enhancing or overriding certain behavior. The wrapper object will delegate any incoming method calls to the original object, unless it defines a new method to enhance or replace the original object’s behavior. By using the decorator pattern, you can dynamically create as many decorated objects as you want, each enhancing the behavior of the original object in a unique way—and all without mutating the original object. In this manner, you can effectively add, remove, or extend behaviors at runtime.
One of the other advantages of the decorator pattern is that wrapped objects can retain the type of the original object. As a result, you can use original and wrapped objects interchangeably, which is a significant advantage when your goal is to write flexible code. In this manner, you can easily extend the behavior of a particular object without modifying the original code.
Undecorated Objects
Before we look at the decorator pattern itself, let’s take a look at some undecorated code to get an idea of its limitations and why we might want to do things differently.
using System;
namespace decorator_0
{
class Program
{
static void Main(string[] args)
{
var msg = "I love C-sharp";
var simpleMsg = new SimpleMessage(msg);
var excitedMsg = new ExcitedMessage(msg);
var quizzicalMsg = new QuizzicalMessage(msg);
var excitedAndQuizzicalMsg = new ExcitedAndQuizzicalMessage(msg);
SimpleMessage[] messages = { simpleMsg, excitedMsg, quizzicalMsg, excitedAndQuizzicalMsg };
foreach (SimpleMessage m in messages)
{
m.PrintMessage();
}
// I love C-sharp
// I love C-sharp!!!
// I love C-sharp???
// I love C-sharp!!!???
}
}
public class SimpleMessage
{
private string Content;
public SimpleMessage(string content)
{
this.Content = content;
}
public string GetMessage()
{
return this.Content;
}
public void PrintMessage()
{
Console.WriteLine(this.GetMessage());
}
}
public class ExcitedMessage : SimpleMessage
{
public ExcitedMessage(string content)
: base(content + "!!!") {}
}
public class QuizzicalMessage : SimpleMessage
{
public QuizzicalMessage(string content)
: base(content + "???") { }
}
public class ExcitedAndQuizzicalMessage : SimpleMessage
{
public ExcitedAndQuizzicalMessage(string content)
: base(content + "!!!???") { }
}
}
In this snippet we have a class hierarchy with a SimpleMessage
at the top. The SimpleMessage
class has a constructor that accepts a content
string as well as two methods: GetMessage
; and, PrintMessage
. Down the hierarchy we have three subclasses: ExcitedMessage
; QuizzicalMessage
; and, ExcitedAndQuizzicalMessage
. The only difference in the subclasses is that they override the SimpleMessage
constructor to change the content
string and append various exclamations. When we instantiate various message objects, using the same content string, and iterate over them, each has their own unique output.
This code works for our purposes but it’s not exactly dynamic. If we wanted our initial simpleMsg
object to sometimes act excited and sometimes quizzical, we could only do so by instantiating entirely new objects from the relevant subclass. Moreover, the ExcitedAndQuizzicalMessage
is really just a combination of ExcitedMessage
and QuizzicalMessage
and probably shouldn’t have its own class. To fix these problems, let’s now return to the decorator pattern and see how it might help us.
Elements of Decoration
The decorator pattern generally calls for four elements, which interact with one another as follows.
- Component Interface: The component interface is an abstraction describing the behaviors of the components that you will eventually use in your program. Any objects that will use these components will do so through the interface, meaning that they are principally concerned with the abstraction (not the actual object). This is what allows both objects and wrapped objects to be considered to be the same type.
- Concrete Components: A concrete component is a component that implements the component interface. In doing so, it agree to the contract set by the interface that it will implement certain behaviors.
- Decorator Abstractions: A decorator abstraction is an abstract class that implements the component interface. Critically, the decorator abstraction must also contain a pointer to some instance of the same interface. Inside the decorator abstraction, each of the component interface behaviors will be delegated to whichever concrete component the pointer indicates.
- Concrete Decorator: A concrete decorator is a subclass of the decorator abstraction. This is where the action of the decorator pattern takes place. A concrete decorator “wraps” over the provided concrete component and may then either override existing behaviors or add new behavior.
Dynamism through Decoration
Going back to our message program, let’s see what it might look like when using the decorator pattern.
using System;
namespace decorator_1
{
class Program
{
static void Main(string[] args)
{
var msg = "I love C-sharp";
var simpleMsg = new SimpleMessage(msg);
var excitedDecoratedMsg = new ExcitedMessageDecorator(simpleMsg);
var quizzicalDecoratedMsg = new QuizzicalMessageDecorator(simpleMsg);
var excitedAndQuizzicalDecoratedMsg = new QuizzicalMessageDecorator(excitedDecoratedMsg);
IMessage[] messages = { simpleMsg, excitedDecoratedMsg,
quizzicalDecoratedMsg, excitedAndQuizzicalDecoratedMsg };
foreach (IMessage m in messages)
{
m.PrintMessage();
}
// I love C-sharp
// I love C-sharp!!!
// I love C-sharp???
// I love C-sharp!!!???
simpleMsg.PrintMessage();
// I love C-sharp
}
}
public interface IMessage
{
string GetMessage();
void PrintMessage();
}
public class SimpleMessage : IMessage
{
private string Content;
public SimpleMessage(string content)
{
this.Content = content;
}
public string GetMessage()
{
return this.Content;
}
public void PrintMessage()
{
Console.WriteLine(this.GetMessage());
}
}
public abstract class MessageDecorator : IMessage
{
private IMessage Message;
public MessageDecorator(IMessage message)
{
this.Message = message;
}
public virtual string GetMessage()
{
return this.Message.GetMessage();
}
public virtual void PrintMessage()
{
this.Message.PrintMessage();
}
}
public class ExcitedMessageDecorator : MessageDecorator
{
public ExcitedMessageDecorator(IMessage message) : base(message) {}
public override string GetMessage()
{
return base.GetMessage() + "!!!";
}
public override void PrintMessage()
{
Console.WriteLine(this.GetMessage());
}
}
public class QuizzicalMessageDecorator : MessageDecorator
{
public QuizzicalMessageDecorator(IMessage message) : base(message) {}
public override string GetMessage()
{
return base.GetMessage() + "???";
}
public override void PrintMessage()
{
Console.WriteLine(this.GetMessage());
}
}
}
In this version, our elements are as follows:
- Component Interface: The
IMessage
class fills the role of the component interface. Inside this interface we set a contract that any implementing classes will haveGetMessage
andPrintMessage
methods. - Concrete Components: Our
SimpleMessage
class remains mostly unchanged from the first version except that it formally implements theIMessage
component interface. Since it implementsIMessage
we will be able to wrap it using any decorator that descends from a decorator abstraction that also implementsIMessage
. - Decorator Abstractions: Our decorator abstraction takes the form of the abstract
MessageDecorator
class, which also implementsIMessage
. TheMessageDecorator
class has a constructor that accepts anIMessage
object as a parameter and then assigns it to a private variable. For its part,MessageDecorator
doesn’t have any special behaviors and simply delegatesGetMessage
andPrintMessage
calls to whicheverIMessage
object was injected into it. - Concrete Decorator: Here we have two concrete decorators,
ExcitedMessageDecorator
andQuizzicalMessageDecorator
, each of which descends from the abstractMessageDecorator
class. However, these decorators do have special behavior in that they override theGetMessage
andPrintMessage
behaviors and enhance them by calling the base version fromMessageDecorator
(which in turn delegates to the relevantIMessage
object) and then appending exclamations.
When we instantiate a SimpleMessage
and then pass it to the various decorators, we now get new behavior. Moreover, since both the concrete component and the concrete decorators all implement / descend from IMessage
, they are interchangeable as far as the program is concerned, meaning that we can loop over them together. Further, rather than having to create a new ExcitedAndQuizzicalMessageDecorator
class, we were able to achieve the same effect by double wrapping a SimpleMessage
object (first in an ExcitedMessageDecorator
and then in a QuizzicalMessageDecorator
). Finally, note that despite having been passed into various decorators, our simpleMsg
object remains unchanged at the end of the program.
Although this short example is a bit contrived, it’s easy to see how we might be able to use this pattern to wrap a single object in various decorations and therefore get it to behave in different ways. More importantly, we could do so even if the original object was immutable to us, such as with a third-party library.
TL;DR
The decorator pattern is a design pattern that allows you to wrap an object such that it will appear to execute a given behavior in many different ways at different points in program execution. This is especially useful when you want an object to have different behaviors at but are unable to mutate the object. The decorator pattern is a useful way to implement dynamic behavior without needing an extended inheritance-based class hierarchy. To use the decorator pattern you need four elements: a component interface; a concrete component; a decorator abstraction; and, a concrete decorator. The interface sets the contract for both component and decorator behavior, the decorator abstraction contains a pointer to some concrete component, and the concrete decorators wrap a concrete component and override behavior if desired.
And that’s the decorator pattern! If you’re interested in learning more about design patterns, check out these articles on the strategy pattern and the abstract factory pattern!
Note: This article was originally published on Medium.