Decorator Design Pattern | Design a coffee machine
Business Requirement:
Design an ordering system for beverage offerings as well as for several condiments. Where beverage can be House Blend, Dark Roast, Decaf, Espresso and condiments can be steamed milk, soy, mocha, chocolate, whipped milk. More beverage or condiments can be added in future, so design should be adaptive.
Note : A customer can order any beverage in a combination with any condiments, for example House Blend with double Soy and mocha. Or Dark Roast with whipped milk.
So ordering system should be flexible enough to calculate the cost based on any combination.
Tech Understanding: Inheriting behaviour at runtime through composition and delegation.
Design Principle: Classes should be open for extension, but closed for modification.
While it may seem like a contradiction, there are techniques for allowing code to be extended without direct modification.
Be careful when choosing the areas of code that need to be extended, applying the Open-Closed Principle. EVERYWHERE is wasteful, unnecessary, and can lead to complex, hard to understand code.
Decorator Design Pattern
It attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
While that describes the role of decorator pattern, it doesn’t give us a lot of insight into how we’d apply the pattern to our own implementation. Let’s take a look at the class diagram, which is a little more revealing.
Coding Beverage: Now that we’ve got our base classes out of the way, let’s implement some beverages. Remember, we need to set a description for specific beverage and also implement the cost method.
Coding Condiments: If you look back at the decorator pattern class diagram, you’ll see we’ve now written our abstract Beverage(Component), we have our concrete HouseBlend(Component), and we have our abstract decorator CondimentDecorator.
Java Code Base
abstract class Beverage {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
class DarkRost extends Beverage {
public DarkRost() {
description = "DarkRost";
}
@Override
public double cost() {
return 2.99;
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
}
@Override
public double cost() {
return 3.99;
}
}
class Mocha extends CondimentDecorator {
private Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return beverage.cost() + .20;
}
@Override
public String getDescription() {
return beverage.getDescription() + " " + "Mocha";
}
}
class Soy extends CondimentDecorator {
private Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return beverage.cost() + .30;
}
@Override
public String getDescription() {
return beverage.getDescription() + " " + "Soy";
}
}
class Whip extends CondimentDecorator {
private Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return beverage.cost() + .25;
}
@Override
public String getDescription() {
return beverage.getDescription() + " " + "Whip";
}
}
public class DecoratorPattern {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " " + beverage.cost());
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " " + beverage2.cost());
Beverage beverage3 = new DarkRost();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription() + " " + beverage3.cost());
}
}