Decorator Design Pattern

Structural Design Pattern

Summary
Decorator design pattern is used to extend or decorate certain objects of a Class (not all objects) statically or at run time. Multiple decorators can act on an object to add the desired behavior to a particular object instance without effecting other instances.
Decorator design pattern is a Structural design pattern, it is alternative to inheritance/ Sub classing in java. Java sub classing effects all the instances of the class, and some times it is not desirable.

Difference between Decorator and Visitor
Decorator design pattern works on a single object.
Decorator design pattern helps in enhancing a single object with new functionality or behaviour.
Decorator is a Structural design pattern, Visitor is a behavioral design pattern.
Visitor works on composite objects.
Visitor pattern is used to add a new algorithm or functionality to collection of objects or hierarchy of objects.

Details
Following are the Participants in this pattern
Component : Interface for object that might have Decorators applied
ConcreateComponent : Concrete objects that implements Component interface
Decorator: Maintains a reference to a Component Object
ConcreateDecorator: Implementations of Decorators that work on the referenced Component object to add behavior to them.

Example
Let's take example of a Coffee store which servers different types of Coffee (Cappuccino, Latte, Mocha etc). Also we can ask for different sizes of coffee (small, large etc).
Customer can also ask for without sugar, with sugar replacement etc.

One option is to have different classes representing different concrete Coffee, say JavaChipsCoffeeTallWithSuger, JavaChipsWithSugarTallWithHone etc.
Easily we can see that the number of different Classes will explode if we want all the customization to be available. This is because with inheritance the Classes should be available at compile time.  Also this makes it tough to support a new kind of coffee type say 'SeasonalPumpikLatte', which would mean to create the new class and recompile the code.

Important Points
With decorator design pattern, we create different decorators to suit the purpose
Each decorator contains a reference to the Object(Component) it want to act on. In our case all the decorators contains a reference to the Coffee object.
All the Decorators implement the same interface Coffee like the original object (DefaultCoffee)
Client should know the available able Decorators, so that they can utilize them to get the result.
For Client they see the same interface Coffee being returned.

In this example we are creating Different Decorators for differently priced and style of the Coffee to be served.

For instance size of the Coffee can have the following Decorators,

Size Decorators:CoffeeTallSizeDecoratorCoffeeSmallSizeDecorator

Coffee type can have the following Decorators,

Coffee Type Decorator:CappuccinoCoffeeDecoratorJavaChipsCoffeeDecorator

Now we can apply a set of Decorators to get our desired cup of Coffee.

Class Diagram



Java Code Sample 



//Decorator Interface
public interface Coffee {

  void brewCofee();

  Double coffeePrice();
}



//Abstract Coffee Decorator
public abstract class CoffeeDecorator implements Coffee {

  private Coffee coffee;

  public CoffeeDecorator(Coffee coffee) {
    this.coffee = coffee;
  }

  public Coffee getCoffee() {
    return coffee;
  }

  public void setCoffee(Coffee coffee) {
    this.coffee = coffee;
  }

  @Override
  public void brewCofee() {
    System.out.println("Cappuccino::: adding raw coffee");
  }

  public Double coffeePrice() {
    return getCoffee().coffeePrice();
  }
}



//CoffeeTallSizeDecorator
public class CoffeeTallSizeDecorator extends CoffeeDecorator {

  public CoffeeTallSizeDecorator(Coffee coffee) {
    super(coffee);

  }


  @Override
  public void brewCofee() {
    getCoffee().brewCofee();
  }

  @Override
  public Double coffeePrice() {
    return getCoffee().coffeePrice() + 3;
  }
}


//JavaChipsCoffeeDecorator
public class JavaChipsCoffeeDecorator extends CoffeeDecorator {

  public JavaChipsCoffeeDecorator(Coffee coffee) {
    super(coffee);
  }

  @Override
  public void brewCofee() {
    // super.brewCofee();
    getCoffee().brewCofee();
    System.out.println("JavaChips:: Adding JavaChips Topping");
  }

  @Override
  public Double coffeePrice() {
    // TODO Auto-generated method stub
    return super.coffeePrice() + 2;
  }
}


//LatteCoffee Decorator
public class LatteCoffeeDecorator extends CoffeeDecorator {
  public LatteCoffeeDecorator(Coffee coffee) {
    super(coffee);
  }

  @Override
  public Double coffeePrice() {
    return getCoffee().coffeePrice() + 10;
  }
}


//Default Coffee
public class DefaultCoffee implements Coffee {

  @Override
  public void brewCofee() {
    System.out.println("### Preparing your coffee  Size :: Small");

  }

  @Override
  public Double coffeePrice() {
    return 1.5;
  }

}

//Demo of the Decorator Design Pattern
public class DecoratorPatternTest {

  public static void main(String[] args) {
    Coffee mycoFfee = new CoffeeTallSizeDecorator(
        new JavaChipsCoffeeDecorator(new CappuccinoCoffeeDecorator(new DefaultCoffee())));
    mycoFfee.brewCofee();
    System.out.println(mycoFfee.coffeePrice());
  }


}

Advantage
Unlike inheritance, you can choose any single object of a class  leaving the other instances unmodified.
We can have different instances of the same Class different behaviour.
With decorators behaviour can be changed by attaching or detaching Decorators.
Since all the Decorators maintain the same Interface, it is very easy to change the Decorator in Client

Disadvantage
Decorator Design Pattern increases the number of Classes.

Github Code


Comments

Popular posts from this blog

Converting Java Map to String

Invoking EJB deployed on a remote machine

Difference between volatile and synchronized