Wikipedia has an example of a decorator pattern here:
https://en.wikipedia.org/wiki/Decorator_pattern#Second_example_.28coffee_making_scenario.29
I was trying to solve this using functional style using Java 8,the solution I came up:
1.CoffeeDecorator.java
public class CoffeeDecorator {
public static Coffee getCoffee(Coffee basicCoffee, Function<Coffee, Coffee>... coffeeIngredients) {
Function<Coffee, Coffee> chainOfFunctions = Stream.of(coffeeIngredients)
.reduce(Function.identity(),Function::andThen);
return chainOfFunctions.apply(basicCoffee);
}
public static void main(String args[]) {
Coffee simpleCoffee = new SimpleCoffee();
printInfo(simpleCoffee);
Coffee coffeeWithMilk = CoffeeDecorator.getCoffee(simpleCoffee, CoffeeIngredientCalculator::withMilk);
printInfo(coffeeWithMilk);
Coffee coffeeWithWSprinkle = CoffeeDecorator.getCoffee(coffeeWithMilk,CoffeeIngredientCalculator::withSprinkles);
printInfo(coffeeWithWSprinkle);
}
public static void printInfo(Coffee c) {
System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
}
}
2.CoffeeIngredientCalculator.java
public class CoffeeIngredientCalculator {
public static Coffee withMilk(Coffee coffee) {
return new Coffee() {
@Override
public double getCost() {
return coffee.getCost() + 0.5;
}
@Override
public String getIngredients() {
return coffee.getIngredients() + " , Milk";
}
};
}
public static Coffee withSprinkles(Coffee coffee) {
return new Coffee() {
@Override
public double getCost() {
return coffee.getCost() + 0.2;
}
@Override
public String getIngredients() {
return coffee.getIngredients() + " , Sprinkles";
}
};
}
}
Now, I am not so convinced with the solution in the CoffeeIngredientCalculator. If we had a single responsibility in the Coffee interface, getCost(), using the functional style and applying the decorator pattern seems a lot better and cleaner. It would basically boil down to a Function<Double,Double>
,we would not need the abstract class, separate decorators and can just chain the functions.
But in the coffee example, with 2 behaviors of the cost and description on the Coffee object, I am not so convinced that this is a significant value addition as we are creating an anonymous class,overriding the 2 methods.
Questions:
1) Is this solution acceptable ?
2) If not, is there a better way to solve it using functional style?
3) Should we stick to the usual GOF way of having an abstract class and separate decorator classes in scenarios where the object that we are decorating has multiple methods?
Decorator is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators. Using decorators you can wrap objects countless number of times since both target objects and decorators follow the same interface.
The Decorator Pattern allows class behavior to the decorated dynamically. It's a structural design pattern as it's used to form large object structures across many disparate objects. The concept of decorator is that it adds additional attributes to an object dynamically.
Decorator design pattern is one of the structural design pattern (such as Adapter Pattern, Bridge Pattern, Composite Pattern) and uses abstract classes or interface with composition to implement.
The decorator design pattern is a structural pattern, which provides a wrapper to the existing class. The decorator design pattern uses abstract classes or interfaces with the composition to implement the wrapper.
So, just quick answers:
Coffee
methods independently. Depends on your requirements. Since Coffee
isn't a functional interface, i.e. has more than 1 method, you have to fall back to the plain old subclassing.Just a final note: If you don't like anonymous classes, then you can write private static
inner classes, or whatever. One is more compact, the other plays better with garbage collector.
Your solution is acceptable.
For practical reasons like single implementation of equals
, hashCode
, toString
I would replace anonymous class with
constructor
return new Coffee(coffee.getCost() + 0.2, coffee.getIngredients() + ", Sprinkles");
factory method
return coffee(coffee.getCost() + 0.2, coffee.getIngredients() + ", Sprinkles");
or even copy methods (like in immutables)
return coffee
.withCost(coffee.getCost() + 0.2) //new instance
.withIngredients(coffee.getIngredients() + ", Sprinkles"); //another new instance
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With