1. Code Reuse with Multiple Decorators?
In this tutorial, we're going to look at how we can use forwarding decorators to enable code reuse when we have a large decorator structure.
The decorator pattern enables us to enrich the existing functionality. A decorator implements an interface and also wraps another implementation. Assume that we have an interface with multiple methods, like java.util.List. So the decorators must implement all the methods adding new functionality only to some of them. If we need additional functionality for a small subset of the methods, the other methods just delegate the operation to the wrapped implementation. If multiple decorators present the same behavior, this leads to code duplication.
2. Forwarding Decorator
Let's look at the details of implementing the forwarding decorator. Conceptually, we'll combine the decorator pattern with inheritance. Firstly, we must define a base decorator class that forwards all calls to the wrapped instance. This base class doesn't add new functionality. Then all other decorators - of the given interface - must extend this base class and override the required methods to add new functionality.
We'll start with the Animal interface:
public interface Animal {
void walk();
void run();
void eat(String meal);
Animal breed(Animal animal);
void sleep();
}
Animal contains five methods for the subclasses to implement.
Next, we'll provide an implementation:
public class Dog implements Animal {
@Override
public void walk() {
System.out.println("Dog is walking.");
}
@Override
public void run() {
System.out.println("Dog is running.");
}
@Override
public void eat(String meal) {
System.out.println("Dog is eating.");
}
@Override
public Animal breed(Animal animal) {
System.out.println("Dog is breeding.");
return new Dog();
}
@Override
public void sleep() {
System.out.println("Dog is sleeping.");
}
}
Here, we have the Dog class. Note that it isn't a decorator and makes no use of delegation. For our purposes, we'll use it to do the actual work.
Then we'll define the base decorator, the ForwardingAnimal class. As we mentioned previously, it just forwards the calls.
public abstract class ForwardingAnimal implements Animal {
private final Animal delegate;
public ForwardingAnimal(Animal delegate) {
this.delegate = delegate;
}
@Override
public void walk() {
delegate.walk();
}
@Override
public void run() {
delegate.run();
}
@Override
public void eat(String meal) {
delegate.eat(meal);
}
@Override
public Animal breed(Animal animal) {
return animal.breed(animal);
}
@Override
public void sleep() {
delegate.sleep();
}
}
An important point is that ForwardingAnimal is abstract and can't be instantiated.
By using the base decorator, we'll build a decorator that counts the breeding occurrences:
public class BreedingAwareDecorator extends ForwardingAnimal {
private final AtomicInteger breedingCount = new AtomicInteger();
public BreedingAwareDecorator(Animal animal) {
super(animal);
}
@Override
public Animal breed(Animal animal) {
Animal baby = super.breed(animal);
System.out.println("Breeding count: " + breedingCount.incrementAndGet());
return baby;
}
}
Here, BreedingAwareDecorator extends ForwardingAnimal and just overrides the breed method for the counting functionality. Since the superclass has the delegation already in place, we don't need to implement other methods. Here lies the main motive of the forwarding decorators. The more decorators we create by extending the ForwardingAnimal class, the more benefit we'll gain because of the code reuse.
Lastly, we have a small application to test the usage:
public static void main(String[] args) {
Animal dogDecorator = new BreedingAwareDecorator(new Dog());
dogDecorator.breed(new Dog());
dogDecorator.breed(new Dog());
dogDecorator.breed(new Dog());
}
A sample run prints:
Dog is breeding.
Breeding count: 1
Dog is breeding.
Breeding count: 2
Dog is breeding.
Breeding count: 3
3. Real-World Examples
The Google Guava library has forwarding decorator implementations like ForwardingList, ForwardingMap, and ForwardingSet.
4. Summary
In this tutorial, we've looked at how we can create a forwarding decorator to reduce code duplication when we have multiple decorators implementing a large interface.
As always, the source code for all examples in this tutorial is available on Github.