《Effective java》读书记录-第16条-复合优先于继承

来源:互联网 发布:淘宝神笔有什么用 编辑:程序博客网 时间:2024/04/30 10:49

继承(当一个类扩展另一个类的时候)是实现代码重用的有力手段,但它不是最好的方式。

与方法调用不同的是,继承打破了封装性(子类依赖与超类中特定功能的实现细节)。

下面的例子为了统计Set的操作次数,通过继承的方式实现的。

public class InstrumentHashSet<E> extends HashSet<E> {        private int addCount=0;                public InstrumentHashSet () {        }        public InstrumentHashSet (int initCap,int loadFactor) {            super(initCap,loadFactor);        }    @Override    public boolean add(E e) {        System.out.println("add");        addCount++;        return super.add(e);    }    @Override    public boolean addAll(Collection<? extends E> c) {        System.out.println("addAll");        addCount+=c.size();        return super.addAll(c);    }    public int getAddCount(){        return addCount;    }}
    public void testFirst(){        InstrumentHashSet<String> set=new InstrumentHashSet<String>();        set.addAll(Arrays.asList("a","b","c"));        System.out.println(set.getAddCount());    }

在测试代码中,通过addAll方式添加数据到Set中。

这段代码看起来没有任何问题,预期的结果是输出3,然而实际结果却是6。

导致这一错误的原因是,HashSet.addAll()方法是遍历传入的集合,然后调用HashSet.add()添加到Set中去。

现在我们发现了错误的原因,那把统计的那条语句注释掉,就可以解决错误了。

@Override    public boolean addAll(Collection<? extends E> c) {        System.out.println("addAll");        //addCount+=c.size();        return super.addAll(c);    }
这个方法虽然目前的Java版本是可用的,如果之后的版本修改这个方法的话,那么程序又会出现新的问题,这就埋下一个隐患了。
那么要怎么来处理这个问题。

新建一个类ForwardingSet,在ForwardingSet中添加一个final的Set私有域,这种设计称为“复合”。
ForwardingSet实现Set的接口,并通过包含的Set的实例去返回这些接口方法,这种设计被称为“转发”。
这样得到的类将会非常稳固,它不依赖于Set类的实现细节。即使Set类添加了新的方法,也不会影响ForwardingSet。

public class InstrumentHashSet<E> extends ForwardingSet<E> {    private int addCount=0;    public InstrumentHashSet (Set<E> s) {        super(s);    }    @Override    public boolean add(E e) {        System.out.println("add");        addCount++;        return super.add(e);    }    @Override    public boolean addAll(Collection<? extends E> c) {        System.out.println("addAll");        addCount+=c.size();        return super.addAll(c);    }    public int getAddCount(){        return addCount;    }}

public class ForwardingSet<E> implements Set<E> {    private final Set<E> set;        public ForwardingSet (Set<E> set) {        this.set=set;    }    public int size() {        return this.set.size();    }    public boolean isEmpty() {        return this.set.isEmpty();    }    public boolean contains(Object o) {        return this.set.contains(o);    }    public Iterator<E> iterator() {        return this.set.iterator();    }    public void clear() {        this.set.clear();    }}

包装类几乎没什么缺点,但不适用于回调框架(callback  framework)

只有当子类真正是超类的子类型(subtype)时(A和B两个类确实存在“is-a”关系),才适合用继承

继承机制会把超类API中的所有缺陷传播到子类中,而复合则允许设计新的API来隐藏这些缺陷。所以在决定使用继承前一定要考虑,试图扩展的类是否有缺陷,是否愿意把那些缺陷传播到类的API中

继承的功能非常强大,但是也存在许多的问题,因为它违背了封装原则。



0 0
原创粉丝点击