Benji's Blog

Java Forwarding-Interface Pattern

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 implements List {
    private List 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  List map(Function 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 implements List, ForwardingList, Mappable {
    private List impl;

    public MappableList(List impl) {
        this.impl = impl;
    }

    @Override
    public List impl() {
        return impl;
    }
}

We can use it like this

// prints 3, twice.
new MappableList(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 extends ForwardingList {
	default  List map(Function 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 extends List {
    List 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 extends ForwardingList {
    default boolean isEmpty() {
        return true;
    }
}
class MappableList implements 
    List, 
    ForwardingList, 
    Mappable, 
    AlwaysEmpty { // Mix in the new interface
    // ...
}

Now our list always claims it’s empty

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