Mícéal Gallagher in Design-patterns 5 minutes

Design Patterns - The Decorator Pattern

Tarbucks needs to add a new beverage to the system. Shouldn’t be a problem! Right? But, it would seem their current class diagram is somewhat overwhelming.

Beverage Disaster

Before blaming the previous developer, let’s try and break this down. We appear to have an abstract superclass- Beverage. Every Beverage sub-type takes care of their cost() implementation. Let’s look at HouseBlend to simplify things.

Beverage House Blend

It seems that a new sub-type gets created when additional condiments are added. Not ideal. We could add our new sub-type, but we’d only be contributing to a duplication problem that plagues this design.

We need to refactor. Let’s start by moving the condiments to the superclass.

Uber Beverage

Let’s bring all the subclasses back and see how things look.

Beverage with sub-types

Not bad. We are down to five classes.

Although, if we need to add a new condiment we’d have to modify the Beverage superclass, which is undesirable. Our design should adhere to the open-closed principle which states; classes should be open for extension but closed for modification. Adding a new condiment each time would certainly violate this principle.

Inheritance hasn’t been serving us well in this scenario; we need an alternative approach. We should start with a Beverage and decorate it with condiments. Let’s say we need a Dark Roast with Mocha and Whip, here is how we would create it:

  1. Make a DarkRoast object
  2. Decorate it with a Mocha object
  3. Decorate it with a Whip object
  4. Call cost() and rely on delegation to add on the condiment costs

Decorate? Think of decorators as “wrappers”. Maybe a visual representation would better serve this concept.

1. Make a DarkRoast object

Base Bird-O-Sim

2. Decorate it with Mocha object

Base Bird-O-Sim

3. Decorate it with Whip object

Base Bird-O-Sim

4. Call cost() and rely on delegation to add on the condiment costs

Base Bird-O-Sim

Let’s try a solution using the decorator pattern. Base Bird-O-Sim

Okay, enough diagrams. Let’s do some code. To begin let’s look at the Beverage class which doesn’t need to be altered from it’s original design. The getDescription() method is defined for us while cost() needs to be implemented by subclasses.

public abstract class Beverage {
  String description = "Unknown beverage";

  public String getDescription() {
    return description;
  }

  public abstract double cost();
}

Now we create the CondimentDecorator. This abstract class needs to be interchangeble with Beverage, so we extend the Beverage class. Why the abstract getDescription() method? We need this to output condiment names.

public abstract class CondimentDecorator extends Beverage {
  public abstract String getDescription();
}

Now we’re done with the base classes, let’s look at the HouseBlend concrete implementation of Beverage. Remember we need to specify description and implement the cost() method.

public class HouseBlend extends Beverage {
  public HouseBlend() {
    description = "House Blend";
  }

  public double cost() {
    return 0.99;
  }
}

We’ve got our abstract component (Beverage), concrete components (HouseBlend) and our abstract decorator (CondimentDecorator). Now we need to implement our concrete decorators. Let’s start with Mocha. We need to extend the CondimentDecorator and store a reference to the Beverage to be decorated. Notice getDescription() and cost(), they both delegate the call to this.beverage before adding their description/cost.

public class Mocha extends CondimentDecorator {
  // The berverage to which the condiment will be applied 
  Beverage beverage;

  public Mocha(Beverage beverage) {
    this.beverage = beverage;
  }

  public String getDescription() {
    return this.beverage.getDescription() + ", Mocha";
  }

  public double cost() {
    // Return the cost plus the condiment cost 
    return this.beverage.cost() + 0.10;
  }
}

Finally we can make a coffee.

Beverage houseBlend = new HouseBlend();
houseBlend = new Whip(houseBlend);
houseBlend = new Mocha(houseBlend);

Beverage darkRoast = new DarkRoast();
darkRoast = new Soy(darkRoast);
darkRoast = new Vanilla(darkRoast);

System.out.println(houseBlend.getDescription() + houseBlend.cost())
System.out.println(darkRoast.getDescription() + darkRoast.cost())

// Outputs:
//   HouseBlend, Mocha, Whip 1.69
//   DarkRoast, Soy, Vanilla 1.97

Final thoughts

  • Inheritance is not the best way to extend functionality. Composition offers greater flexibility
  • It’s possible through a coding error to wrap the concrete component incorrectly and not call the outmost wrapper
  • Due to the above point, the decorator pattern should be used in combination with the factory pattern
  • The decorator can result in a lot of subclasses which can get confussing for new developers
  • Care should be taken that client code doesn’t rely on a single concrete component always use abstractions. In our example client code should use the abstract class Beverage