BlackWasp
Design Patterns
.NET 1.1+

Decorator Design Pattern

The decorator pattern is a design pattern that extends the functionality of individual objects by wrapping them with one or more decorator classes. These decorators can modify existing members and add new methods and properties at run-time.

What is the Decorator Pattern?

The decorator pattern is a Gang of Four design pattern. This is a structural pattern as it defines a manner for creating relationships between classes or entities. The decorator pattern is used to extend or alter the functionality of objects at run-time by wrapping them in an object of a decorator class. This provides a flexible alternative to using inheritance to modify behaviour.

The decorator pattern is used to extend the functionality of individual objects, not classes. This means that the modifications are made at run-time rather than at design time. This allows changes to be applied to objects in response to specific conditions such as user-selected options and business rules. It also means that several objects based upon the same underlying class can be decorated in different manners. In addition, as both the class of the object being modified and the class of the decorator share a base class, multiple decorators can be applied to the same object to incrementally modify behaviour.

An example of the decorator pattern could be used in a computer system used by a company that provides vehicles for hire during track days. Such a company could own a fleet of cars and motorcycles for hire. Each vehicle could be represented by an instance of a class that inherits from the Vehicle class. This would define key properties such as make, model, hire price and number of laps permitted.

Vehicles in the track day system will not be available for hire if they have faults. However, if no faults are known, the vehicle's object would be wrapped by the Hireable class. This class would be a decorator that holds a reference to the underlying vehicle and adds extra functionality to allow logging of the details of hire periods. Further decorators could be added according to business rules. For example, special offer decorators could be added to automatically reduce the hire price or increase the number of laps that may be driven.

Implementing the Decorator Pattern

Decorator Design Pattern UML

The UML class diagram above describes an implementation of the decorator design pattern. The items in the diagram are described below:

  • ComponentBase. This abstract class is the base class for both the concrete components and all decorator classes. The base class defines any standard members that will be implemented by these classes. If you do not wish to create any actual functionality in this class you may decide to create it as an interface instead.
  • ConcreteComponent. The ConcreteComponent class inherits form the ComponentBase class. There may be multiple concrete component classes, each defining a type of object that may be wrapped by the decorators.
  • DecoratorBase. This abstract class is the base class for all decorators for components. The class inherits its public interface from ComponentBase so that decorators can be used in place of concrete objects. It adds a constructor that accepts a ComponentBase object as its parameter. The passed object is the component that will be wrapped. As the wrapped object must inherit from ComponentBase, it may be a concrete component or another decorator. This allows for multiple decorators to be applied to a single object. When calls to the DecoratorBase's members are made, these are passed unmodified to the matching member of the wrapped object.
  • ConcreteDecorator. The ConcreteDecorator class provides a decorator for components. In the diagram, an additional method has been included in the decorator. The Operation member can be implemented in two manners. Firstly, the operation may be unchanged. In this case, the implementation simply calls the base method. Secondly, the underlying operation may be modified or replaced completely. In this case, new functionality will be added, which may or may not call the base method.

The following shows the basic code of the decorator design pattern implemented using C#.

public abstract class ComponentBase
{
    public abstract void Operation();
}


public class ConcreteComponent : ComponentBase
{
    public override void Operation()
    {
        Console.WriteLine("Component Operation");
    }
}


public abstract class DecoratorBase : ComponentBase
{
    private ComponentBase _component;

    public DecoratorBase(ComponentBase component)
    {
        _component = component;
    }

    public override void Operation()
    {
        _component.Operation();
    }
}


public class ConcreteDecorator : DecoratorBase
{
    public ConcreteDecorator(ComponentBase component) : base(component) { }

    public override void Operation()
    {
        base.Operation();
        Console.WriteLine("(modified)");
    }
}

Example Decorator

Earlier in this article I described an example use of the decorator pattern for a track-day hire system. In the remainder of the article we will implement the skeleton of such an object model. In the example we will define a vehicle class that holds details of a car, its hire charge and number of laps permitted. We will also create two decorator classes. The first decorator will be used to describe special offers by modifying the lap count and price. The second will add the method required to log the details of a hire period. We will then demonstrate the effects of the two wrappers.

The code involved in creating the entire functionality for this object model would be rather large so it will be limited to outputting messages to the console describing the state and behaviour of the objects. The code for the base classes and a concrete vehicle class is as follows. Note that C# 3.0 automatically implemented properties syntax has been used for brevity. For earlier versions of the language you will need to implement the properties and backing variables in full:

public abstract class VehicleBase
{
    public abstract string Make { get; }
    public abstract string Model { get; }
    public abstract double HirePrice { get; }
    public abstract int HireLaps { get; }
}


public class Ferrari360 : VehicleBase
{
    public override string Make
    {
        get { return "Ferrari"; }
    }

    public override string Model
    {
        get { return "360"; }
    }

    public override double HirePrice
    {
        get { return 100; }
    }

    public override int HireLaps
    {
        get { return 10; }
    }
}


public abstract class VehicleDecoratorBase : VehicleBase
{
    private VehicleBase _vehicle;

    public VehicleDecoratorBase(VehicleBase vehicle)
    {
        _vehicle = vehicle;
    }

    public override string Make
    {
        get { return _vehicle.Make; }
    }

    public override string Model
    {
        get { return _vehicle.Model; }
    }

    public override double HirePrice
    {
        get { return _vehicle.HirePrice; }
    }

    public override int HireLaps
    {
        get { return _vehicle.HireLaps; }
    }
}

Special Offer Decorator

The first decorator class will allow a special offer to be applied to the hiring of a vehicle. The class will include two additional properties to those in the base class. These will define a discount percentage and a value that increases the number of laps that may be driven during a hire period. The HirePrice and HireLaps properties will be overridden to include these modifiers. The code for the decorator is as follows:

public class SpecialOffer : VehicleDecoratorBase
{
    public SpecialOffer(VehicleBase vehicle) : base(vehicle) { }

    public int DiscountPercentage { get; set; }
    public int ExtraLaps { get; set; }

    public override double HirePrice
    {
        get
        {
            double price = base.HirePrice;
            int percentage = 100 - DiscountPercentage;
            return Math.Round((price * percentage) / 100, 2);
        }
    }

    public override int HireLaps
    {
        get
        {
            return base.HireLaps + ExtraLaps;
        }
    }
}

Hireable Decorator

The second vehicle decorator will not modify the underlying properties of the vehicle object it wraps. Instead, it will add a new method that records a hire period. To demonstrate the Hire method, the class will output the name of the person hiring the vehicle and the details of the vehicle, price and number of laps purchased to the console.

public class Hireable : VehicleDecoratorBase
{
    public Hireable(VehicleBase vehicle) : base(vehicle) { }

    public void Hire(string name)
    {
        Console.WriteLine("{0} {1} hired by {2} at a price of {3:c} for {4} laps."
            , Make, Model, name, HirePrice, HireLaps);
    }
}

Testing the Decorators

To test the decorators we can use a new console application. Add the code shown below to the Main method then execute the program. The test code creates a new vehicle and outputs its details to the console. Next, a special offer decorator is added to the car and the modified pricing and laps permitted is outputted.

To demonstrate the second decorator, a Hireable object is use to wrap to the car object and the Hire method is called. This demonstrates that the Hire method has been added to the vehicle at run-time. Finally, the vehicle with a special offer applied is wrapped with a Hireable decorator and the Hire method is called. The output of this call demonstrates the stacking of two decorators on a single object.

NB: The output may differ from that displayed as the formatting will show your preferred currency symbol.

// Basic vehicle
Ferrari360 car = new Ferrari360();

Console.WriteLine("Base price is {0:c} for {1} laps."
    , car.HirePrice, car.HireLaps);

// Special offer
SpecialOffer offer = new SpecialOffer(car);
offer.DiscountPercentage = 25;
offer.ExtraLaps = 2;

Console.WriteLine("Offer price is {0:c} for {1} laps."
    , offer.HirePrice, offer.HireLaps);

// Hire for basic vehicle
Hireable hire1 = new Hireable(car);
hire1.Hire("Bob");

// Hire for vehicle with special offer
Hireable hire2 = new Hireable(offer);
hire2.Hire("Bill");

/* OUTPUT

Base price is £100.00 for 10 laps.
Offer price is £75.00 for 12 laps.
Ferrari 360 hired by Bob at a price of £100.00 for 10 laps.
Ferrari 360 hired by Bill at a price of £75.00 for 12 laps.

*/
Link to this Page15 February 2009
TwitterTwitter RSS Feed RSS