Codiwan.com

The blog for Design Patterns, Linux, HA and Myself!

Decorator Pattern With Real World Example In Java

Decorator Pattern or Decorator Design Pattern: Learn Design Pattern Decorator with a Real World Example by creating a Coffee Ordering System

In this tutorial we’ll look into the Decorator Design Pattern. We’ll see the benefits of Decorator Design Pattern by creating a Coffee Ordering System as there will be some design issues that we’ll encounter, and then we’ll fix them by using the Decorator Design Pattern.

Class Explosion

So, this Coffee Ordering System has multiple types of Coffees, like, House Blend, Espresso. Along with the coffee there are condiments as well, like, Mocha, Milk, Soy and Whipped Milk.

This means that there can be Espresso with Streamed Milk and Mocha or House Blend with soy and whip. Actually, the number of Beverages that this system can make is pretty high which is equal to the combination of each of the coffee type and condiments available.

The problem is to add all these types of beverages into the Coffee ordering system so that it can take orders for all the available beverages.

How to do it?

Well, the very first solution that comes to the mind is to create classes of all these different types of beverages. And that’s what we’ll do right now.

We’ll create an abstract class Beverage and the classes HouseBlend, DarkRoast, HouseBlendWithMochaAndStreamedMilk will implement it. We are just creating 4 classes only for this tutorial, however, there are a number of classes that can be created depending upon the combination of the Coffee and the condiments.

The abstract Beverage class:

// Super class of all the beverages.
abstract class Beverage {
    
private String description;

    Beverage() {
        description = "Sample Beverage.";
    }

    public String getDescription() {
        return this.description;
    }

    // this method is abstract because the different types of beverages will have different cost
    public abstract double cost();
}

Our HouseBlend Coffee with its cost:

class HouseBlend extends Beverage {

    private String description;

    HouseBlend() {
        this.description = "House Blend";
    }

    public double cost() {
        return 10.5;
    }
}

The DarkRoast Coffee with its cost:

class DarkRoast extends Beverage {

    private String description;

    DarkRoast() {
        this.description = "Dark Roast";
    }

    public double cost() {
        return 5.6;
    }
}

House Blend Coffee With Mocha And Streamed Milk. This item contains both, the coffee and the condiment.

class HouseBlendWithMochaAndStreamedMilk extends Beverage {

   private String description;

   HouseBlendWithMochaAndStreamedMilk() {
       this.description = "House Blend With Mocha And Streamed Milk";
   }

   @Override
   public String getDescription() {
       return this.description;
   }

   @Override
   public double cost() {
       return 11.45;
   }
}

Does this solution look promising?

Well, these all these classes can be added into the Coffee ordering system but after implementing this easy solution we can see that there are a lot of issues present in this solution:

One of the problems that we faced in the previous stage is the huge number of class. We have to bring it down!

It can be observed that there are 4 condiments and it’s just their availability status that is contributing to this class explosion. We can try to bring down the number of classes available by adding the condiment availability into the super class, Beverage.

class Beverage {

    private boolean milk;

    private boolean soy;

    private boolean mocha;

    private boolean whip;

    void setMilk() {
        this.milk = true;
    }

    void setSoy() {
        this.soy = true;
    }

    void setMocha() {
        this.mocha = true;
    }

    void setWhip() {
        this.whip = true;
    }

    double cost() {
        return 0D + (this.milk ? 1 : 0) +
                (this.soy ? 1 : 0) + (this.mocha ? 1 : 0) +
                (this.whip ? 1 : 0);
    }
}

This abstract Beverage class adds the cost of the condiments that are applied. Afterwards, the concrete coffee types can implement the Beverage.cost() to add their cost as well.

This way we don’t require a lot of classes which also solves our maintenance problem.

Our refurbished House Blend:

class HouseBlend extends Beverage {

    double cost() {
        return 10.5 + super.cost();
    }
}

And the refurbished Dark Roast:

class DarkRoast extends Beverage {

    double cost() {
        return 5.6 + super.cost();
    }
}

Now that we’ve provided a fix for it can we assume that we’ve solved our problems? Well, sort of. But we’ve added some more 😔.

Let’s look into the current problems:

Coffee Explosion

The problem that we encountered in the previous stage is the ever changing Beverage class. Whenever a new type of condiment will be added then the Beverage class will require a change.

And it violates the principle:

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

If we keep on changing the parent class then concrete classes will also have to handle the changes. Also, there are methods which do not seem fit for the classes, like, whip() for the BlackCoffee class.

This make the user to keep a watch on the usage of this method and it is bad because this method should not even be available to be used in the first place for this class. So solve these problems let’s look into the Decorator Design Pattern.

Class Diagram for Decorator

This class diagram resembles our new code structure.

The classes Espresso, House Blend and Dark Roast extends Beverage class.

The refurbished(again?) Beverage class

abstract class Beverage {
    String description = "Beverage";

    String getDescription() {
        return description;
    }

    public abstract double cost();
}

The Espresso Coffee class

class Espresso extends Beverage {

    Espresso() {
        this.description = "Espresso";
    }

    @Override
    public double cost() {
        return 5.7;
    }
}

The House Blend Coffee Class:

class HouseBlend extends Beverage {

    HouseBlend() {
        this.description = "House Blend Coffee";
    }

    @Override
    public double cost() {
        return 3.8;
    }
}

Then there’s one more class, CondimentDecorator, that will be extended by the Condiments.

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

Our Mocha condiment:

class Mocha extends CondimentDecorator {

    private Beverage beverage;

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

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

    @Override
    public double cost() {
        return beverage.cost() + 1.5D;
    }
}

and the Whip condiment:

class Whip extends CondimentDecorator {

    private Beverage beverage;

    Whip(Beverage beverage) {
        this.beverage = beverage;
    }

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

    @Override
    public double cost() {
        return beverage.cost() + 0.5D;
    }
}

The condiments require a beverage because the condiments cannot be served exclusively and this requirements makes the beverage a part of the constructor so that the condiments creation cannot complete without a beverage.

It can be noticed that the Condiments have a has-a relationship with the Beverages although they are also a beverage.

The condiments wrap a beverage and afterwards they are wrapped again by other condiments. This makes the condiment a decorator as they are decorating the beverages. The decorated beverages can further be decorated by other decorators.

Behaviour Diagram

Once a condiment wraps a beverage it add the cost of itself to the cost of the beverage. For example, Espresso with Double Mocha and Whip will have Espresso as a beverage and Mocha * 2(as it is Double Mocha) and Whip as the condiment decorators.

Finally the definition from the Wikipedia

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

I’ve created these tutorials after learning Design Patterns from this book Head First Design Patterns (A Brain Friendly Guide).

Loading Comments... Disqus Loader
comments powered by Disqus