Java 8’s default methods on interfaces means we can implement the decorator pattern much less verbosely.

The decorator pattern allows us to add behaviour to an object without using inheritance. I often find myself using it to “extend” third party interfaces with useful additional behaviour.

Let’s say we wanted to add a map method to List that allows us to convert from a list of one type to a list of another. There is already such a method on the Stream interface, but it serves as an example

We used to have to either

a) Subclass a concrete List implementation and add our method (which makes re-use hard), or
b) Re-implement the considerably large List interface, delegating to a wrapped List.

You can ask your IDE to generate these delegate methods for you, but with a large interface like List the boilerplate tends to obscure the added behaviour.


class MappingList<T> implements List<T> {
    private List<T> impl;
 
    public int size() {
        return impl.size();
    }
 
    public boolean isEmpty() {
        return impl.isEmpty();
    }
 
    // Many more boilerplate methods omitted for brevity
 
    // The method we actually wanted to add.
    public <R> List<R> map(Function<T,R> f) {
        return list.stream().map(f).collect(Collectors.toList());
    }
 
}

Guava gave us a third option

c) Extend the the Guava ForwardingList class. Unfortunately that meant you couldn’t extend any other class.

Java 8 gives us a fourth option

d) We can implement the forwarding behaviour in an interface, and then add our behaviour on top.

The disadvantage is you need a public method which exposes the underlying implementation. The advantages are you can keep the added behaviour separate, and it’s easier to compose them.

Our decorator can now be really short – something like

class MappableList<T> implements List<T>, ForwardingList<T>, Mappable<T> {
    private List<T> impl;
 
    public MappableList(List<T> impl) {
        this.impl = impl;
    }
 
    @Override
    public List<T> impl() {
        return impl;
    }
}

We can use it like this

// prints 3, twice.
new MappableList<String>(asList("foo", "bar"))
    .map(s -> s.length())
    .forEach(System.out::println);

The new method we added is declared in its own Mappable<T> interface which is uncluttered.

interface Mappable<T> extends ForwardingList<T> {
	default <R> List<R> map(Function<T,R> f) {
		return impl().stream().map(f).collect(Collectors.toList());
	}
}

The delegation boilerplate we can keep in its own interface, out of the way. Since it’s an interface we are free to extend other classes/interfaces in our decorator

interface ForwardingList<T> extends List<T> {
    List<T> impl();
 
    default int size() {
        return impl().size();
    }
 
    default boolean isEmpty() {
        return impl().isEmpty();
    }	
 
    // Other methods omitted for brevity
 
}

If we wanted to mix in some more functionality to our MappableList decorator class we could just implement another interface. In the above example we added a new method, so this time let’s modify one of the existing methods on List. Let’s make a List that always thinks it’s empty.

interface AlwaysEmpty<T> extends ForwardingList<T> {
    default boolean isEmpty() {
        return true;
    }
}
class MappableList<T> implements 
    List<T>, 
    ForwardingList<T>, 
    Mappable<T>, 
    AlwaysEmpty<T> { // Mix in the new interface
    // ...
}

Now our list always claims it’s empty

// prints true
System.out.println(new MappableList<String>(asList("foo", "bar")).isEmpty());

One Response to “Java Forwarding-Interface Pattern”

Leave a Reply

  • (will not be published)