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

来源:互联网 发布:python 函数 编辑:程序博客网 时间:2024/04/30 12:48

在系统分析设计这门课中某王就不停地强调一点,就是ISA(is a 原则)。都知道继承是面向对象的一大要素,但是哪里使用复合哪里使用继承,其实还是有讲究的。

可以简单的用ISA原则概括。有一个具备一定功能类,我们要对其功能进行拓展,到底是采用复合呢还是继承呢?当新类与旧类的关系是从属关系是,即cat is an animal,English book is a book,我们优先使用继承;当新类是旧类的组成部分之一时,即hand is a part of body,jiangsu is a part of China,我们优先使用复合。

理由如下:当我们要扩展一个类时,特别是一个别人写好的类,一个类库的类,我们往往关心的仅仅是单个api的功能,而不关心他的实现,但是存在的一个问题就是,同一个类的各个方法直接可能存在联系,可能一个方法的实现依赖于另一个方法,这就意味着,当我们调用一个我们想要操作的方法时,“继承”会隐式的调用另一个方法,这就可能存在问题。

经典的例子是Set中add()和addAll()的内在联系。
需求:新建一个集合类,维护一个addCount变量,记录,一共添加了多少次新值。分别用继承,复合实现。
先看继承:
MySet.java

package cczu.edu.test2;import java.util.Collection;import java.util.HashSet;public class MySet<E> extends HashSet<E>{    private int addCount = 0;    public MySet() {    }    @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;    }}

Test

@Test    public void test1(){        MySet<String> set = new MySet<String>();        List<String> list = new ArrayList<String>();        list.add("abc");        list.add("def");        list.add("ghi");        set.addAll(list);        System.out.println(set.getAddCount());        //the ans is 6    }

因为,在hashSet的addAll()实现中,是循环调用add()方法的,所以导致3*2。当然你也可以重写addAll()方法,但是这样就失去了继承的意义。

使用复合:
ForwardingSet.java

package cczu.edu.test2;import java.util.Collection;import java.util.HashSet;import java.util.Iterator;import java.util.Set;public class ForwardingSet<E> implements Set<E> {    private final Set<E> s;    public ForwardingSet(Set<E> s) {        this.s = s;    }    public ForwardingSet(){        this.s = new HashSet<E>();    }    @Override    public int size() {        return s.size();    }    @Override    public boolean isEmpty() {        return s.isEmpty();    }    @Override    public boolean contains(Object o) {        return s.contains(o);    }    @Override    public Iterator<E> iterator() {        return s.iterator();    }    @Override    public Object[] toArray() {        return s.toArray();    }    @Override    public <T> T[] toArray(T[] a) {        return s.toArray(a);    }    @Override    public boolean add(E e) {        return s.add(e);    }    @Override    public boolean remove(Object o) {        return s.remove(o);    }    @Override    public boolean containsAll(Collection<?> c) {        return s.containsAll(c);    }    @Override    public boolean addAll(Collection<? extends E> c) {        // TODO Auto-generated method stub        return false;    }    @Override    public boolean retainAll(Collection<?> c) {        return s.retainAll(c);    }    @Override    public boolean removeAll(Collection<?> c) {        return s.removeAll(c);    }    @Override    public void clear() {        s.clear();    }}

Myset2.java

package cczu.edu.test2;import java.util.Collection;public class MySet2<E> extends ForwardingSet<E>{    private int addCount = 0;    @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;    }}

Test

@Test    public void test2(){        MySet2<String> set = new MySet2<String>();        List<String> list = new ArrayList<String>();        list.add("abc");        list.add("def");        list.add("ghi");        set.addAll(list);        System.out.println(set.getAddCount());        //the ans is 3    }

增加一个ForwardingSet.java,作用仅仅就是实现转发。

回过头来看,复合可以将旧类完全地包裹起来,以至于我们不需要关注旧类的实现,同时达到可以调用旧类方法的效果,避免了旧类各个方法之间存在的联系,再加入旧类如果还有其他的权限问题,复合也可以隐藏缺陷。

自我总结:其实设计模式中例如代理模式,适配器模式,装饰器模式,和继承、复合都有些联系,以至于我都有一点混淆,毕竟,设计模式是死的,思想是活的。

0 0
原创粉丝点击