第16条:复合优先于继承
继承是实现代码重用的有力手段,但是使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,子类和超类的实现都处在同一个程序员的控制之下。对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。然而们对于进行跨越包边界的继承,则要非常小心。“继承”在这里特指一个类扩展另一个类。
public class InstrumentedHashSet<E> extends HashSet<E> { 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; } public static void main(String[] args) { InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList("s","a","p")); System.out.println(s.getAddCount()); } }
考虑上面的提供计数功能的HashSet。我们希望得到的结果是3,但结果却是6,原因是addAll方法是基于add方法实现的,InstrumentedHashSet的addAll方法首先将addCount加3,然后super.addAll调用HashSet的addAll方法,而addAll又分别调用到InstrumentedHashSet覆盖了的add方法,每个元素调用一次,又给addCount加了3,结果是6。
只要去掉覆盖的addAll方法就可以修正问题,虽然这个类可以正常工作了,但是HashSet的addAll方法是实现细节,不是承诺,意味着不能保证addAll的实现方法在未来发行的版本保持不变。
稍微好一点的方法是覆盖addAll方法来遍历指定的集合,为每个元素调用一次add方法,这样可以保证得到正确的结果,然而这种方法对于要访问超类的私有域就无能为力了。
覆盖超类的每一个添加元素的方法,如果超类以后增加一种新的添加元素方法,那就可能有“非法的元素”添加到集合中。
不扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,可以避免前面提到的问题,这种设计称为“复合”,因为现有的类变成了新类的一个组件,新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果。
public class ForwardingSet<E> implements Set<E> { private Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contain(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e); } public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c); } public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } public Object[] toArray() { return s.toArray(); } public boolean equals(Object o) { return s.equals(o); } public int hashCode() { return s.hashCode(); } public String toString() { return s.toString(); } @Override public boolean contains(Object o) { // TODO Auto-generated method stub return false; } @Override public <T> T[] toArray(T[] a) { // TODO Auto-generated method stub return null; } @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); } }
public class InstrumentedSet<E> extends ForwardingSet<E> { private int addCount = 0; public InstrumentedSet(Set<E> s) { super(s); } public boolean add(E e) { addCount++; return super.add(e); } public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } }
ForwardingSet作为一个转发类,拥有一个Set实例变量,而InstrumentedSet把add和addAll方法覆盖掉,实现计数功能。