Decorator Pattern

Welcome to Starbuzz Coffee

You can ask for several condiments, and here is their first attemp...

class explosion!!!

Better version

Potential Problems?

1. Price changes for condiments will force us to alter existing code.

2. New condiments will force us to add new methods and alter the cost method in the superclass.

3. We may have new beverages. For some of these beverages(iced tea?), the condiments may not be appropriate, yet the Tea subclass will still inherit methods like hasWhip().

4. What if a customer wants a double mocha?

The Open-Closed Principle

Classes should be open for extension, but closed for modification.

Designs that are resilient to change and flexible enough to take on new functionality to meet changing requirement.

But....

Applying the Open-Closed Principle EVERYWHERE is wasteful, unnecessary, and can lead to complex, hard to understand code.

Meet the Decorator Pattern

For example, if the customer wants a Dark Roast with Mocha and Whip, then we'll...

1. Take a DarkRoast object

2. Decorate it with a Mocha object.

3. Decorate it with a Whip object.

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

Constructing a drink order with Decorators

We start with our DarkRoast object.

The decorator adds its own behavior either before and/or after delegating to the object it decorates to do the rest of the job.

Compute the cost for the customer

The Decorator Pattern defined

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

Decorating our Beverages

Writing the Starbuzz code

public abstract class Beverage {
    String description = "Unknown Beverage";
    
    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }

    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "House Blend Coffee";
    }

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

Coding condiments

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

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

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

Serving some coffees

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + "$" + beverage.cost());

        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());

        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println(beverage2.getDescription() + "$" + beverage3.cost());
    }
}

Real World Decorators: Java I/O

BufferedInputStream and LineNumberInputStream both extend FilterInputStream, which acts as the abstract decorator class.

Decorating the java.io classes

Decorating Pattern

By TingSheng Lee

Decorating Pattern

  • 370