Effective Java 第16条 : 复合优先于继承

来源:互联网 发布:l女装淘宝店名 编辑:程序博客网 时间:2024/04/30 14:15

   继承(指的是子类扩展超类,并不包含接口)是实现代码重用的有力手段,但它并不总是完成这项工作的最佳工具。不适当地使用继承会导致脆弱的软件。 

    与方法调用不同的是,继承打破了封装性。换句话说子类依赖于超类中特定功能的实现细节。超类的实现可能随着发行版本而变化,就有可能影响子类。因此,子类必须要跟着超类的更新而发展。除非超类是专门为扩展而设计的,并且具有很好的说明文档。

    

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;    }}
这段代码如果打印 addCount的值的话:6。

由于 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)。为了避免这种脆弱性,可以用复合和转发机制来代替继承,尤其是在适当的接口中可以实现包装类。包装类不仅比子类更加健壮,而且功能更强大。