继承(指的是子类扩展超类,并不包含接口)是实现代码重用的有力手段,但它并不总是完成这项工作的最佳工具。不适当地使用继承会导致脆弱的软件。
与方法调用不同的是,继承打破了封装性。换句话说子类依赖于超类中特定功能的实现细节。超类的实现可能随着发行版本而变化,就有可能影响子类。因此,子类必须要跟着超类的更新而发展。除非超类是专门为扩展而设计的,并且具有很好的说明文档。
这段代码如果打印 addCount的值的话:6。public class InstrumentedHashSet<E> extends HashSet<E> { private int addCount = 0; public InstrumentedHashSet() { } public InstrumentedHashSet(int intCap, float loadFactor) { super(intCap, 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; } }
由于 HashSet 中的 addAll方法的实现也是通过自身的add方法来实现的。这样会导致子类很脆弱,由于超类在后续的发布版本中可以获得新的方法。
上面两个问题都来源于覆盖(Override)动作。如果在扩展一个类的时候,仅仅是增加新的方法,而不是覆盖现有的方法,你可能认为是安全的。虽然这种扩展方式比较安全一些,但是也并非完全没有风险。如果超类在后续的发行版本中获得了一个新的方法,并且不幸的是跟你的方法
重名 。实际是你还是覆盖了超类的方法,因此又回到刚才的两个问题上去了 。
我们 可以不用扩展现有的类,而是在新类中增加一个私有域,它引用现有类的一个实例。这种设计被称作”复合(composition)“,因为现有类变成了新类的一个组件。
public class ForwardingSet<E> implements Set<E> { private final Set<E> s;//新类中增加一个私有域 public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(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 removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } public Object[] toArray() { return s.toArray(); } public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean equals(Object obj) { return s.equals(obj); //To change body of overridden methods use File | Settings | File Templates. } @Override public int hashCode() { return s.hashCode(); //To change body of overridden methods use File | Settings | File Templates. } @Override public String toString() { return s.toString(); //To change body of overridden methods use File | Settings | File Templates. } }
public class InstrumentedHashSet<E> extends HashSet<E> { private int addCount = 0; public InstrumentedHashSet() { } public InstrumentedHashSet(int intCap, float loadFactor) { super(intCap, 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; } }
简而言之,继承的功能非常强大,但是也存在诸多问题,因为他违背了封装原则。只有当子类和超类之间全是存在子类型关系时,使用继承才是恰当的。即便如此,如果子类和超类不在同一个包中,并且超类不是为了继承而设计的,那么继承会导致脆弱性(fragility)。为了避免这种脆弱性,可以用复合和转发机制来代替继承,尤其是在适当的接口中可以实现包装类。包装类不仅比子类更加健壮,而且功能更强大。