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 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,作用仅仅就是实现转发。

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

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

时间: 2024-10-06 00:28:54

Effective Java学习--第16条:复合优先于继承的相关文章

Effective Java学习--第21条:用函数对象表示策略

搞ACM大多数是使用C++,用java开发后总感觉对数据处理及输入输出控制不适应,仔细一想,其实是java没有指针的锅.在C++中,如果要实现结构体的自定义排序是非常简单的. #include <algorithm> #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <vect

Effective Java总结的78条

1.考虑用静态工厂方法代替构造器 2.遇到多个构造器参数时要考虑用构造器 3.用私有构造器或者枚举类型强化Singleton属性 4.通过私有构造器强化不可实例化的能力 5.避免创建不必要的对象 6.消除过期的对象引用 7.避免使用finalizer方法 8.重写equals方法时遵守通用约定 9.重写equals时总要重写hashCode 10.始终要重写toString 11.谨慎的重写clone 12.考虑实现Comparable接口 13.使类和成员的可访问性最小化 14.在共有类中使用

Effective java读书札记第一条之 考虑用静态工厂方法代替构造器

对于类而言,为了让客户端获取它资深的一个实例,最常用的方法就是提供一个共有的构造器.还有一种放你发,也应该子每个程序员的工具箱中占有一席之地.类可以提供一个共有的静态 工厂方法,它只是返回类的实例的静态方法. 类可以通过静态工厂方法类提供它的客户端(对象),而不是通过构造器.提这样做的好处有: 1.静态工厂方法与构造器不同的第一大优势在于,它们有名称.比如构造器BigInteger(int,int,Random)返回的BigInteger可能为素数,如果用名为BigInteger.probabl

Effective Java 学习笔记之第七条——避免使用终结(finalizer)方法

避免使用终结方法(finalizer) 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的. 不要把finalizer当成C++中析构函数的对应物.java中,当对象不可达时(即没有引用指向这个对象时),会由垃圾回收器来回收与该对象相关联的内存资源:而其他的内存资源,则一般由try-finally代码块来完成类似的工作. 一.finalizer的缺点: 1. 终结方法的缺点在于不能保证会被及时地执行. 及时执行finalizer方法是JVM垃圾回收方法的一个主要功

第16条:复合优先于继承

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

Java学习笔记16(面向对象九:补充内容)

总是看到四种权限,这里做一个介绍: 最大权限是public,后面依次是protected,default,private private修饰的只在本类可以使用 public是最大权限,可以跨包使用,不同包的子类和无关类都可以使用,可以修饰类,方法,成员变量 不写权限就是default默认权限:限于本包内使用 protected权限:跨包后的类如果有继承关系(子类),不能使用default修饰的,而可以使用protected修饰的,调用时候必须在子类的里面才可以调用父类的受保护权限,注意prote

Effective Java学习笔记

创建和销毁对象 第一条:考虑用静态工厂方法替代构造器 For example: public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; } 优势: 有名称 不必在每次调用它们的时候都创建一个新对象 它们可以返回原返回类型的任何子类型的对象 在创建参数化类型实例的时候,他们使代码变得更加简洁 缺点: 类如果不含公有的或者受保护的构造器,就不能被子类化 它们与其他的静态方法实际上没有任何区别

我的java学习笔记(16)关于对象克隆与调回

1.当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,改变一个变量所引用的对象将会对另一个变量产生影响. a a1 = new a(); a a2 = a1; a2.up(10);//a1也会改变 2.如果创建一个对象的新的copy,它的状态与原来的对象一样,但以后可以各自改变各自的状态,那就需要使用clone方法. a a1 = new a(); a a2 = a1.clone(); a2.up(10);//a1不会改变了 3.如果对象中的所有数据域都属于数值或基本类型,这样的拷贝域没有任何

我的java学习笔记(16)关于内部类(part 1)

1.内部类是定义在另一个类中的类. 原因:a.内部类方法可以访问类定义所在的作用域中的数据,包括私有的数据. b.内部类可以对同一个包中的其他类隐藏起来. c.当想要定义一个回调函数且不想编写大量的代码时,使用匿名内部类比较便捷. 2.内部类皆可以访问自身的数据域,也可以访问创建它的外围类对象的数据域. 3.内部类的对象总有一个隐式引用,它指向了创建它的外部类对象.这个引用在内部类的定义中是不可见的. 4.外围类的设置一般都在内部类的构造器中设置. 5.只用内部类可以是私有类,而常规类只可以具有