java – Composing and Inheriting from the Same Type


To start off with an example: I have a read-only Repository used for getting arbitrary values. This behavior can be implemented multiple ways.

I also want to allow opt-in mutation of the repository’s values through a MutableRepository, which implements Repository because it satisfies Liskov substitution principle (any repository that supports writing should support reading). MutableRepository also has multiple implementations.

At the same time, I do not want to couple the implementation of writing to the implementation of reading. Given:

interface Repository<T> {
    T getValue(String valueID);
}

Declaring MutableRepository as:

interface MutableRepository<T> extends Repository<T> {
    void setValue(String valueID, T value);
}

forces any implementor of MutableRepository to handle implementation of getValue. Whereas, if I do this:

abstract class MutableRepository<T> implements Repository<T> {
    private final Repository<T> baseRepository;

    MutableRepository(Repository<T> baseRepository) {
        this.baseRepository = baseRepository;
    }

    @Override
    public T getValue(String valueID) {
        return baseRepository.getValue(valueID);
    }

    abstract void setValue(String valueID, T value);
}

I allow implementations of MutableRepository to only handle implementing setValue. Given three ways of writing to a repository and three ways of reading to a repository that can be mixed and matched:

  • The former way of declaring MutableRepository forces 3 * 3 = 9 different implementations of MutableRepository / Repository since an implementation of setValue is coupled to getValue.

  • The latter way of declaring MutableRepository forces only 3 + 3 = 6 different implementations of MutableRepository / Repository, since an implementation of getValue can be injected into that of setValue.

Composing MutableRepository from Repository while inheriting from Repository at the same time seems the only way to support LSP while decoupling implementation of the reading from writing. But I’ve always seen composition and inheritance presented as alternatives (and if they are used together, not over the same type), instead of being combined like this.

Is there a different approach I should take here?