The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality

The Decorator Pattern provides an alternative to subclassing for extending behavior. This is possible with a set of decorator classes that are used to wrap concrete components. These Decorator classes are of the same type as the components and therefore mirror the type of the components they decorate. This is done either through inheritance or interface implementation. Decorators change the behavior of their components by adding new functionality before and/or after (or even in place of) method calls to the component.

Using the decorator design pattern can result in many small objects, and overuse can be complex. On its own, the decorator pattern adds a lot of small classes to a design which can be hard to understand. It also has typing problems which arise when code is dependent on a specific type, which is destroyed when using decorators. Decorators are typically transparent to the client of the component. That is, unless the client is relying on the component’s concrete type. To avoid this drawback other design patterns such as the factory pattern are helpful.

Extensible and at the same time closed design with the decorator pattern.

In the following example Beverage acts as a abstract component class.

public abstract class Beverage {
	public enum Size { TALL, GRANDE, VENTI };
	Size size = Size.TALL;
	String description = "Unknown Beverage";

	public String getDescription() {
		return description;
	}

	public void setSize(Size size) {
		this.size = size;
	}

	public Size getSize() {
		return this.size;
	}

	public abstract double cost();
}

The concrete components implement this interface.

public class Espresso extends Beverage {

	public Espresso() {
		description = "Espresso";
	}

	public double cost() {
		return 1.99;
	}
}

Another concrete component.

public class DarkRoast extends Beverage {
	public DarkRoast() {
		description = "Dark Roast Coffee";
	}

	public double cost() {
		return .99;
	}
}

To extend the behavior of these concrete components the following condiment decorator inherits the Beverage base class and has a member of type Beverage.

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

	public Size getSize() {
		return beverage.getSize();
	}
}

Using these classes it is possible to wrap the concrete components which gives them new behaviors. Note that this is not obtained through direct inheritance. The next two code snippets show concrete decorators that implement the CondimentDecorator interface.

public class Whip extends CondimentDecorator {
	public Whip(Beverage beverage) {
		this.beverage = beverage;
	}

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

	public double cost() {
		return beverage.cost() + .10;
	}
}

Another condiment decorator.

public class Soy extends CondimentDecorator {
	public Soy(Beverage beverage) {
		this.beverage = beverage;
	}

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

	public double cost() {
		double cost = beverage.cost();
		if (beverage.getSize() == Size.TALL) {
			cost += .10;
		} else if (beverage.getSize() == Size.GRANDE) {
			cost += .15;
		} else if (beverage.getSize() == Size.VENTI) {
			cost += .20;
		}
		return cost;
	}
}

And another one.

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

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

	public double cost() {
		return beverage.cost() + .20;
	}
}

To instantiate a concrete component, or in this example, a beverage and decorate it with condiments, the following main class can be executed.

public class StarbuzzCoffee {

	public static void main(String args[]) {
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription()
				+ " $" + String.format("%.2f", beverage.cost()));

		Beverage beverage2 = new DarkRoast();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDescription()
				+ " $" + String.format("%.2f", beverage2.cost()));

		Beverage beverage3 = new HouseBlend();
		beverage3.setSize(Size.VENTI);
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDescription()
				+ " $" + String.format("%.2f", beverage3.cost()));
	}
}

Starting this application results in the following output.

$ java StarbuzzCoffee
Espresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34

Comments