Introduction
Decorator (also referred as Wrapper) is classified by GoF
as a structural pattern. Its purpose is to:
“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”
Both inheritance and the decorator pattern aim towards extending the functionality. This is what they have in common.
There are a couple of remarkable differences:
- Inheritance extends the functionality at compilation time (statically). The decorator pattern extends the functionality at runtime (dynamically).
- Inheritance extends the functionality of a whole class (all objects of the extended class get the extended functionality). The decorator pattern allows extending the functionality of a selected object (or group of objects) without affecting the remaining objects.
You can think of the decorator pattern as a way to add make-up to an object or even as a way to attach accessories to that object. All this is done on the fly after the object itself has been created.
Let’s walk through a simple task to get the idea. This example might sound silly. I want it silly so that you can focus on the decorator implementation, avoiding any extra complexity.
Please, be aware that this design is somewhat unfinished, since we are only covering the case for a single decoration (just one functionality to be extended). In real life, we will need multiple decorators in order to add multiple responsibilities. As this is a controlled example (just for the purpose of this discussion), I am enforcing that only ONE responsibility is going to be extended. This means, we will have ONE decorator class. Because of that, I have made some simplifications to the design; so that you get a taste of the decorator pattern in its simplest expression.
Later on, in other post, we’ll see how to add multiple responsibilities (with multiple decorator classes). For that, we’ll need a more complex design to overcome the shortcomings of this initial example. For now, just get the idea...we'll come back later to the multiple decorations scenario.
If you get some time, take a look at the discussion in the comments section.
Subtask 1
Let’s create a TConsole
class whose purpose is to output a given text to the standard output. The code might be something like this:
interface
type
TConsole = class
public
procedure Write(aText: string);
end;
implementation
procedure TConsole.Write(aText: string);
begin
Writeln(aText);
end;
Subtask 2
Let’s use the TConsole
class to printout “Hello World!
”. The following code snippet does it:
var
MyConsole: TConsole;
begin
MyConsole:= TConsole.Create;
try
MyConsole.Write('Hello World!');
finally
MyConsole.Free;
end;
Readln;
end.
This is how the output looks like:
Hello World!
Subtask 3
Now, let’s decorate the object referenced by MyConsole
(only that object, not the whole class). What I want is to upper case every text to be printed out. We need to define a decorator class TUpperCaseConsole
for that purpose. See the code:
interface
uses
SysUtils;
type
TConsole = class
public
procedure Write(aText: string); virtual;
end;
TUpperCaseConsole = class(TConsole)
private
FConsole: TConsole;
public
constructor Create(aConsole: TConsole);
destructor Destroy; override;
procedure Write(aText: string); override;
end;
implementation
procedure TConsole.Write(aText: string);
begin
Writeln(aText);
end;
constructor TUpperCaseConsole.Create(aConsole: TConsole);
begin
inherited Create;
FConsole:= aConsole;
end;
destructor TUpperCaseConsole.Destroy;
begin
FConsole.Free;
inherited;
end;
procedure TUpperCaseConsole.Write(aText: string);
begin
aText:= UpperCase(aText);
FConsole.Write(aText);
end;
end;
Notice in the code above that the decorator class (TUpperCaseConsole
) inherits from the decorated class (TConsole
). This makes both the decorated and the decorator objects share the same public
interface. Furthermore, the TUpperCaseConsole
class Has-A field of the TConsole
type. We’ll use this field to forward the printing functionality to the TConsole
class, once we have applied the cosmetic (upper case transformation) to the text.
Subtask 4
Let’s now create some consuming code to decorate one TConsole
object on the fly. Note how the TUpperCaseConsole
constructor wraps (decorates) the object referenced by MyConsole
.
var
MyConsole: TConsole;
begin
MyConsole:= TConsole.Create;
MyConsole:= TUpperCaseConsole.Create(MyConsole);
try
MyConsole.Write('Hello World!');
finally
MyConsole.Free;
end;
Readln;
end.
This is how the output looks like after the decorator has been applied:
HELLO WORLD!
In real life, you’ll have to judge whether the decorator pattern is the best alternative to be applied to solve a particular problem. Not always is it the right way to go with. For more details, get your hands on these classic books.