I'm reading the "Effective Java" book which suggests to favor composition over inheritance. The example it gives shows something like this:
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() { }
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
This presents a problem because the implementation of addAll
in HashSet
uses the method add
. So, when calling addAll
each new elements is counted twice - once in addAll
and once in add
.
In the next chapter it is explained that if we choose to allow extending our class we should explicitly explain the inner working of our methods that use overridable methods (meaning - in the addAll
documentation we should specify its use of add
and commit to this implementation forever).
I think that a better practice would be to decide that each method that is both an overridable, API method and also used by inner implementation should be extracted to an inner, private method. So Our ideal HashSet
would have these methods:
public class HashSet<E> implements Set<E> {
// Ignore irrelevant code
@Override public boolean add(E e) {
// The new add implementation only wraps the innerAdd method
return innerAdd(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
// Do the same logic as before but use innerAdd instead of add
}
private boolean innerAdd(E e) {
// The original add logic will be moved to here
}
}
This way we encapsulate inner implementation and allow extending the class without fearing to override a method which is used by inner implementation. Of course that we can use the original add as before because by calling super.add(e)
we de-facto use the innerAdd
so that's not a problem.
I would like to know whether this might be a good practice (not for HashSet<E>
which is already committed to the code that use it, but to new classes that are meant to be overriden)?