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 ofMutableRepository
/Repository
since an implementation ofsetValue
is coupled togetValue
.The latter way of declaring
MutableRepository
forces only 3 + 3 = 6 different implementations ofMutableRepository
/Repository
, since an implementation ofgetValue
can be injected into that ofsetValue
.
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?