Decorator

Another well-known design pattern is the decorator pattern. This pattern allows us to add behavior to an object without affecting other objects of that class. Quite often this behavior is composable with several subtypes.

A good example is food. Everybody has their own preferences in tastes and compositions. Let's take coffee as an example. We can drink just plain black coffee, with milk, with sugar, with both milk and sugar, or even with syrup, cream, or whatever will be popular in the future. And that's not taking into account the different ways of how to brew coffee.

The following shows a realization of the decorator pattern using plain Java.

We specify the following Coffee type which can be decorated using the sub-type CoffeeGarnish:

public interface Coffee {

    double getCaffeine();
    double getCalories();
}

public class CoffeeGarnish implements Coffee {

    private final Coffee coffee;

    protected CoffeeGarnish(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public double getCaffeine() {
        return coffee.getCaffeine();
    }

    @Override
    public double getCalories() {
        return coffee.getCalories();
    }
}

The default coffee garnish just delegates to its parent coffee. There may be several implementations of a coffee:

public class BlackCoffee implements Coffee {

    @Override
    public double getCaffeine() {
        return 100.0;
    }

    @Override
    public double getCalories() {
        return 0;
    }
}

Besides regular black coffee, we also specify some garnishes:

public class MilkCoffee extends CoffeeGarnish {

    protected MilkCoffee(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCalories() {
        return super.getCalories() + 20.0;
    }
}

public class SugarCoffee extends CoffeeGarnish {

    protected SugarCoffee(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCalories() {
        return super.getCalories() + 30.0;
    }
}

public class CreamCoffee extends CoffeeGarnish {

    protected CreamCoffee(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCalories() {
        return super.getCalories() + 100.0;
    }
}

Using the coffee types, we can compose our desired coffee with its specific behavior:

Coffee coffee = new CreamCoffee(new SugarCoffee(new BlackCoffee()));
coffee.getCaffeine(); // 100.0
coffee.getCalories(); // 130.0

An example for the decorator pattern in the JDK is the InputStream class with the possibility to add specific behavior for files, byte arrays, and so on.

In Java EE, we again make use of CDI which ships with a decorator functionality. Decorators add specific behavior to a bean. Invocations on an injected bean call the decorator instead of the actual bean; the decorator adds specific behavior and delegates to the bean instance. The original bean type becomes a so-called delegate of the decorator:

public interface CoffeeMaker {
    void makeCoffee();
}

public class FilterCoffeeMaker implements CoffeeMaker {

    @Override
    public void makeCoffee() {
        // brew coffee
    }
}

The delegate type must be an interface. The CountingCoffeeMaker decorates the existing coffee maker functionality:

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.enterprise.inject.Any;

@Decorator public class CountingCoffeeMaker implements CoffeeMaker { private static final int MAX_COFFEES = 3; private int count; @Inject @Any @Delegate CoffeeMaker coffeeMaker; @Override public void makeCoffee() { if (count >= MAX_COFFEES) throw new IllegalStateException("Reached maximum coffee limit."); count++; coffeeMaker.makeCoffee(); } }

The decorator functionality is activated via the beans.xml descriptor.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
    <decorators>
        <class>com.example.coffee.CountingCoffeeMaker</class>
    </decorators>
</beans>

After activating the decorator, injected instances of the CoffeeMaker type use the decorated functionality instead. This happens without changing the original implementation:

public class CoffeeConsumer {

    @Inject
    CoffeeMaker coffeeMaker;

    ...
}

Managed beans can have several decorators. If necessary, ordering can be specified on the decorators using the Java EE @Priority annotation.

This CDI functionality applies to managed beans. Depending on whether we want to add additional behavior to our domain model classes or the services involved, we will use the pattern either with plain Java, as described first, or by using CDI decorators.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.79.84