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

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

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

    

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值