第16条:复合优先于继承

继承是实现代码重用的有力手段,但是使用不当会导致软件变得脆弱。在包的内部使用继承是非常安全的,子类和超类的实现都处在同一个程序员的控制之下。对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。然而们对于进行跨越包边界的继承,则要非常小心。“继承”在这里特指一个类扩展另一个类。

public class InstrumentedHashSet<E> extends HashSet<E> {

    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, 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;
    }

    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
        s.addAll(Arrays.asList("s","a","p"));
        System.out.println(s.getAddCount());
    }

}

考虑上面的提供计数功能的HashSet。我们希望得到的结果是3,但结果却是6,原因是addAll方法是基于add方法实现的,InstrumentedHashSet的addAll方法首先将addCount加3,然后super.addAll调用HashSet的addAll方法,而addAll又分别调用到InstrumentedHashSet覆盖了的add方法,每个元素调用一次,又给addCount加了3,结果是6。

只要去掉覆盖的addAll方法就可以修正问题,虽然这个类可以正常工作了,但是HashSet的addAll方法是实现细节,不是承诺,意味着不能保证addAll的实现方法在未来发行的版本保持不变。

稍微好一点的方法是覆盖addAll方法来遍历指定的集合,为每个元素调用一次add方法,这样可以保证得到正确的结果,然而这种方法对于要访问超类的私有域就无能为力了。

覆盖超类的每一个添加元素的方法,如果超类以后增加一种新的添加元素方法,那就可能有“非法的元素”添加到集合中。

不扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,可以避免前面提到的问题,这种设计称为“复合”,因为现有的类变成了新类的一个组件,新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果。

public class ForwardingSet<E> implements Set<E> {
    private Set<E> s;

    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

    public void clear() {
        s.clear();
    }
    public boolean contain(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 retainAll(Collection<?> c) {
        return s.retainAll(c);
    }
    public Object[] toArray() {
        return s.toArray();
    }
    public boolean equals(Object o) {
        return s.equals(o);
    }
    public int hashCode() {
        return s.hashCode();
    }
    public String toString() {
        return s.toString();
    }
    @Override
    public boolean contains(Object o) {
        // TODO Auto-generated method stub
        return false;
    }
    @Override
    public <T> T[] toArray(T[] a) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public boolean removeAll(Collection<?> c) {
        return s.removeAll(c);
    }

}
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;
    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }

}

ForwardingSet作为一个转发类,拥有一个Set实例变量,而InstrumentedSet把add和addAll方法覆盖掉,实现计数功能。

时间: 2024-10-22 09:07:37

第16条:复合优先于继承的相关文章

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

在系统分析设计这门课中某王就不停地强调一点,就是ISA(is a 原则).都知道继承是面向对象的一大要素,但是哪里使用复合哪里使用继承,其实还是有讲究的. 可以简单的用ISA原则概括.有一个具备一定功能类,我们要对其功能进行拓展,到底是采用复合呢还是继承呢?当新类与旧类的关系是从属关系是,即cat is an animal,English book is a book,我们优先使用继承:当新类是旧类的组成部分之一时,即hand is a part of body,jiangsu is a par

第四章:类和接口。ITEM16:复合优先于继承。

1 package com.twoslow.cha4; 2 3 import java.util.Collection; 4 import java.util.HashSet; 5 6 /** 7 *这里我们需要扩展 HashSet 类,提供新的功能用于统计当前集合中元素的数量, 8 *实现方法是新增一个私有域变量用于保存元素数量, 9 *并每次添加新元素的方法中更新该值, 10 *再提供一个公有的方法返回该值 11 */ 12 public class InstrumentedHashSet<

Android开发的16条小经验总结

Android开发的16条小经验总结,希望对各位搞Android开发的朋友有所帮助. 1. TextView中的getTextSize返回值是以像素(px)为单位的, 而setTextSize()是以sp为单位的. 所以如果直接用返回的值来设置会出错,解决办法是用setTextSize()的另外一种形式,可以指定单位: setTextSize(int unit, int size)    TypedValue.COMPLEX_UNIT_PX : Pixels    TypedValue.COMP

提高工作效率的16条Android开发小经验

笔者在经历了多个Android开发项目之后,个人积累也从别处学习了很多在Android开发中非常实用的小经验.下面从中选择了最实用的16条,分享给大家. 1.TextView中的getTextSize返回值是以像素(px)为单位的,而setTextSize()是以sp为单位的.所以如果直接用返回的值来设置会出错,解决办法是用setTextSize()的另外一种形式,可以指定单位: 1 2 3 4 <span style="font-size:16px;">setTextSi

Effective Item 11 - 复合模式优于继承

继承是实现代码重用的方法之一,但使用不当则会导致诸多问题. 继承会破坏封装性,对一个具体类进行跨包访问级别的继承很危险. 即,子类依赖父类的实现细节. 如果父类的实现细节发生变化,子类则可能遭到破坏. 举个例子,扩展HashSet,记录HashSet实例创建以来一共进行了多少次添加元素的操作. HashSet有两个添加元素的方法--add(E e)和addAll(Collection<? extends E> c). 那就覆盖这两个方法,在添加操作执行前记录次数: public class I

第26条:优先考虑泛型

考虑第6条的简单堆栈实现: public class Stack { pprivate Object[] elements; private int size = 0; private static final int DEFAULT_INITAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITAL_CAPACITY]; } public void push(Object e) { ensureCapacity

Effective Java - 复合模式优于继承

继承是实现代码重用的方法之一,但使用不当则会导致诸多问题. 继承会破坏封装性,对一个具体类进行跨包访问级别的继承很危险. 即,子类依赖父类的实现细节. 如果父类的实现细节发生变化,子类则可能遭到破坏. 举个例子,扩展HashSet,记录HashSet实例创建以来一共进行了多少次添加元素的操作. HashSet有两个添加元素的方法——add(E e)和addAll(Collection<? extends E> c). 那就覆盖这两个方法,在添加操作执行前记录次数: public class I

转:ibatis常用16条SQL语句

1.输入参数为单个值 <delete id="com.fashionfree.stat.accesslog.deleteMemberAccessLogsBefore" parameterClass="long"> delete from MemberAccessLog where accessTimestamp = #value# </delete> <delete id="com.fashionfree.stat.acces

涨姿势!北京地铁原来是16条旅游专线

学姐按:周末想带孩子玩,又没太多时间.好不容易有时间出去了,结果半天都被堵在了路上,这让很多家长每逢周末必发愁.为什么不换种方式出行呢?北京的地铁的里程越来越长,殊不知所经之处有很多的大小景点可以玩.今天学姐就带大家细数那些地铁沿线的美景. 一号线 八角游乐园站 石景山游乐园 票价:门票10元,游乐项目另收费 地址:石景山区石景山路 玉泉路站 北京国际雕塑公园 票价:旺季10元,淡季5元 地址:石景山区石景山路2号 军事博物馆站 中华世纪坛 票价:免费 地址:海淀区复兴路甲9号(中央电视台与军事