Decorator
The decorator pattern helps to add behavior or responsibilities to an object. This is also called “Wrapper”.
Examples
Cost
This pattern can be very expensive. You should only use it when it is really necessary. You should have lots of different behaviors and responsibilities for the same class.
Creation
This pattern is expensive to create.
Maintenance
This pattern can be expensive to maintain. If the representation of a class often changes, you will have lots of refactoring.
Removal
This pattern is hard to remove too.
Advises
- Put the decorator term in the name of the decorator classes to indicate the use of the pattern to the other developers.
Implementations
This example illustrates a simple extension method for a bool type.
using System;
// Extension methods must be parts of static classes.
static class BooleanExtensionMethodSample
{
public static void Main()
{
bool yes = true;
bool no = false;
// Toggle the booleans! yes should return false and no should return true.
Console.WriteLine(yes.Toggle());
Console.WriteLine(no.Toggle());
}
// The extension method that adds Toggle to bool.
public static bool Toggle(this bool _target)
{
// Return the opposite of the _target.
return !_target;
}
}
Coffee making scenario
# include <iostream>
# include <string>
// The abstract coffee class
class Coffee
{
public:
virtual double getCost() = 0;
virtual std::string getIngredient() = 0;
virtual ~Coffee() {}
};
// Plain coffee without ingredient
class SimpleCoffee:public Coffee
{
private:
double cost;
std::string ingredient;
public:
SimpleCoffee()
{
cost = 1;
ingredient = std::string("Coffee");
}
double getCost()
{
return cost;
}
std::string getIngredient()
{
return ingredient;
}
};
// Abstract decorator class
class CoffeeDecorator:public Coffee
{
protected:
Coffee & decoratedCoffee;
public:
CoffeeDecorator(Coffee & decoratedCoffee):decoratedCoffee(decoratedCoffee){}
~CoffeeDecorator() {
delete &decoratedCoffee;
}
};
// Milk Decorator
class Milk:public CoffeeDecorator
{
private:
double cost;
public:
Milk(Coffee & decoratedCoffee):CoffeeDecorator(decoratedCoffee)
{
cost = 0.5;
}
double getCost()
{
return cost + decoratedCoffee.getCost();
}
std::string getIngredient()
{
return "Milk "+decoratedCoffee.getIngredient();
}
};
// Whip decorator
class Whip:public CoffeeDecorator
{
private:
double cost;
public:
Whip(Coffee & decoratedCoffee):CoffeeDecorator(decoratedCoffee)
{
cost = 0.7;
}
double getCost()
{
return cost + decoratedCoffee.getCost();
}
std::string getIngredient()
{
return "Whip "+decoratedCoffee.getIngredient();
}
};
// Sprinkles decorator
class Sprinkles:public CoffeeDecorator
{
private:
double cost;
public:
Sprinkles(Coffee & decoratedCoffee):CoffeeDecorator(decoratedCoffee)
{
cost = 0.6;
}
double getCost()
{
return cost + decoratedCoffee.getCost();
}
std::string getIngredient()
{
return "Sprinkles "+decoratedCoffee.getIngredient();
}
};
// Here's a test
int main()
{
Coffee* sample;
sample = new SimpleCoffee();
sample = new Milk(*sample);
sample = new Whip(*sample);
std::cout << sample->getIngredient() << std::endl;
std::cout << "Cost: " << sample->getCost() << std::endl;
delete sample;
}
The output of this program is given below:
Whip Milk Coffee
Cost: 2.2
// Class to be decorated
function Coffee() {
this.cost = function() {
return 1;
};
}
// Decorator A
function Milk(coffee) {
this.cost = function() {
return coffee.cost() + 0.5;
};
}
// Decorator B
function Whip(coffee) {
this.cost = function() {
return coffee.cost() + 0.7;
};
}
// Decorator C
function Sprinkles(coffee) {
this.cost = function() {
return coffee.cost() + 0.2;
};
}
// Here's one way of using it
var coffee = new Milk(new Whip(new Sprinkles(new Coffee())));
alert( coffee.cost() );
// Here's another
var coffee = new Coffee();
coffee = new Sprinkles(coffee);
coffee = new Whip(coffee);
coffee = new Milk(coffee);
alert(coffee.cost());
fun printInfo(c: Coffee) {
println("Cost: " + c.cost + "; Ingredients: " + c.ingredients)
}
fun main(args: Array<String>) {
var c: Coffee = SimpleCoffee()
printInfo(c)
c = WithMilk(c)
printInfo(c)
c = WithSprinkles(c)
printInfo(c)
}
// The interface Coffee defines the functionality of Coffee implemented by decorator
interface Coffee {
val cost: Double // Returns the cost of the coffee
val ingredients: String // Returns the ingredients of the coffee
}
// Extension of a simple coffee without any extra ingredients
class SimpleCoffee : Coffee {
override val cost: Double
get() = 1.0
override val ingredients: String
get() = "Coffee"
}
// Abstract decorator class - note that it implements Coffee interface
abstract class CoffeeDecorator(private val decoratedCoffee: Coffee) : Coffee {
override// Implementing methods of the interface
val cost: Double
get() = decoratedCoffee.cost
override val ingredients: String
get() = decoratedCoffee.ingredients
}
// Decorator WithMilk mixes milk into coffee.
// Note it extends CoffeeDecorator.
class WithMilk(c: Coffee) : CoffeeDecorator(c) {
override// Overriding methods defined in the abstract superclass
val cost: Double
get() = super.cost + 0.5
override val ingredients: String
get() = super.ingredients + ", Milk"
}
// Decorator WithSprinkles mixes sprinkles onto coffee.
// Note it extends CoffeeDecorator.
class WithSprinkles(c: Coffee) : CoffeeDecorator(c) {
override val cost: Double
get() = super.cost + 0.2
override val ingredients: String
get() = super.ingredients + ", Sprinkles"
}
The output of this program is given below:
Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
First Example (window/scrolling scenario)
The following Java example illustrates the use of decorators using the window/scrolling scenario.
// the Window abstract class
public abstract class Window {
public abstract void draw(); // draws the Window
public abstract String getDescription(); // returns a description of the Window
}
// extension of a simple Window without any scrollbars
class SimpleWindow extends Window {
public void draw() {
// draw window
}
public String getDescription() {
return "simple window";
}
}
The following classes contain the decorators for all Window classes, including the decorator classes themselves.
// abstract decorator class - note that it extends Window
abstract class WindowDecorator extends Window {
protected Window decoratedWindow; // the Window being decorated
public WindowDecorator (Window decoratedWindow) {
this.decoratedWindow = decoratedWindow;
}
public void draw() {
decoratedWindow.draw(); //delegation
}
public String getDescription() {
return decoratedWindow.getDescription(); //delegation
}
}
// the first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
public VerticalScrollBarDecorator (Window decoratedWindow) {
super(decoratedWindow);
}
@Override
public void draw() {
super.draw();
drawVerticalScrollBar();
}
private void drawVerticalScrollBar() {
// draw the vertical scrollbar
}
@Override
public String getDescription() {
return super.getDescription() + ", including vertical scrollbars";
}
}
// the second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
public HorizontalScrollBarDecorator (Window decoratedWindow) {
super(decoratedWindow);
}
@Override
public void draw() {
super.draw();
drawHorizontalScrollBar();
}
private void drawHorizontalScrollBar() {
// draw the horizontal scrollbar
}
@Override
public String getDescription() {
return super.getDescription() + ", including horizontal scrollbars";
}
}
Here's a test program that creates a Window instance which is fully decorated (i.e., with vertical and horizontal scrollbars), and prints its description:
public class DecoratedWindowTest {
public static void main(String[] args) {
// create a decorated Window with horizontal and vertical scrollbars
Window decoratedWindow = new HorizontalScrollBarDecorator (
new VerticalScrollBarDecorator(new SimpleWindow()));
// print the Window's description
System.out.println(decoratedWindow.getDescription());
}
}
The output of this program is "simple window, including vertical scrollbars, including horizontal scrollbars". Notice how the getDescription method of the two decorators first retrieve the decorated Window's description and decorates it with a suffix.
Second Example (coffee making scenario)
The next Java example illustrates the use of decorators using coffee making scenario. In this example, the scenario only includes cost and ingredients.
// The abstract Coffee class defines the functionality of Coffee implemented by decorator
public abstract class Coffee {
public abstract double getCost(); // returns the cost of the coffee
public abstract String getIngredients(); // returns the ingredients of the coffee
}
// extension of a simple coffee without any extra ingredients
public class SimpleCoffee extends Coffee {
public double getCost() {
return 1;
}
public String getIngredients() {
return "Coffee";
}
}
The following classes contain the decorators for all Coffee classes, including the decorator classes themselves..
// abstract decorator class - note that it extends Coffee abstract class
public abstract class CoffeeDecorator extends Coffee {
protected final Coffee decoratedCoffee;
protected String ingredientSeparator = ", ";
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
public double getCost() { // implementing methods of the abstract class
return decoratedCoffee.getCost();
}
public String getIngredients() {
return decoratedCoffee.getIngredients();
}
}
// Decorator Milk that mixes milk with coffee
// note it extends CoffeeDecorator
class Milk extends CoffeeDecorator {
public Milk(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() { // overriding methods defined in the abstract superclass
return super.getCost() + 0.5;
}
public String getIngredients() {
return super.getIngredients() + ingredientSeparator + "Milk";
}
}
// Decorator Whip that mixes whip with coffee
// note it extends CoffeeDecorator
class Whip extends CoffeeDecorator {
public Whip(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() {
return super.getCost() + 0.7;
}
public String getIngredients() {
return super.getIngredients() + ingredientSeparator + "Whip";
}
}
// Decorator Sprinkles that mixes sprinkles with coffee
// note it extends CoffeeDecorator
class Sprinkles extends CoffeeDecorator {
public Sprinkles(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() {
return super.getCost() + 0.2;
}
public String getIngredients() {
return super.getIngredients() + ingredientSeparator + "Sprinkles";
}
}
Here's a test program that creates a Coffee instance which is fully decorated (i.e., with milk, whip, sprinkles), and calculate cost of coffee and prints its ingredients:
public class Main {
public static final void main(String[] args) {
Coffee c = new SimpleCoffee();
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
c = new Milk(c);
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
c = new Sprinkles(c);
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
c = new Whip(c);
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
// Note that you can also stack more than one decorator of the same type
c = new Sprinkles(c);
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
}
}
The output of this program is given below:
Cost: 1.0 Ingredient: Coffee
Cost: 1.5 Ingredient: Coffee, Milk
Cost: 1.7 Ingredient: Coffee, Milk, Sprinkles
Cost: 2.4 Ingredient: Coffee, Milk, Sprinkles, Whip
Window System
# the Window base class
class Window(object):
def draw(self, device):
device.append('flat window')
def info(self):
pass
# The decorator pattern approch
class WindowDecorator:
def __init__(self, w):
self.window = w
def draw(self, device):
self.window.draw(device)
def info(self):
self.window.info()
class BorderDecorator(WindowDecorator):
def draw(self, device):
self.window.draw(device)
device.append('borders')
class ScrollDecorator(WindowDecorator):
def draw(self, device):
self.window.draw(device)
device.append('scroll bars')
def test_deco():
# The way of using the decorator classes
w = ScrollDecorator(BorderDecorator(Window()))
dev = []
w.draw(dev)
print dev
test_deco()
Difference between subclass method and decorator pattern
# The subclass approch
class BorderedWindow(Window):
def draw(self, device):
super(BorderedWindow, self).draw(device)
device.append('borders')
class ScrolledWindow(Window):
def draw(self, device):
super(ScrolledWindow, self).draw(device)
device.append('scroll bars')
# combine the functionalities using multiple inheritance.
class MyWindow(ScrolledWindow, BorderedWindow, Window):
pass
def test_muli():
w = MyWindow()
dev = []
w.draw(dev)
print dev
def test_muli2():
# note that python can create a class on the fly.
MyWindow = type('MyWindow', (ScrolledWindow, BorderedWindow, Window), {})
w = MyWindow()
dev = []
w.draw(dev)
print dev
test_muli()
test_muli2()
Coffee example
type Coffee() =
abstract member Cost : double
abstract member Ingredients: string list
default this.Cost = 1.0
default this.Ingredients = ["Coffee"]
type CoffeeDecorator(coffee: Coffee) =
inherit Coffee ()
override this.Cost = coffee.Cost
override this.Ingredients = coffee.Ingredients
type WithMilk(coffee: Coffee) =
inherit CoffeeDecorator(coffee)
override this.Cost = base.Cost + 0.5
override this.Ingredients = ["Milk"] |> List.append base.Ingredients
type WithSprinkles(coffee: Coffee) =
inherit CoffeeDecorator(coffee)
override this.Cost = base.Cost + 0.2
override this.Ingredients = ["Sprinkles"] |> List.append base.Ingredients
let print (coffee: Coffee) =
printfn "Cost: %.2f$; Ingredients: %A" coffee.Cost coffee.Ingredients
let withAddins = Coffee() |> WithMilk |> WithSprinkles
print withAddins
Dynamic languages
The decorator pattern can also be implemented in dynamic languages either with interfaces or with traditional OOP inheritance.
External links