读《Effective java 中文版》(17)

读《Effective java 中文版》(17)

第16条:接口优于抽象类
  Java语言中的接口与抽象类的一些区别:

  • 抽象类允许包含某些方法的实现,接口不允许。
  • 为实现一个由抽象类定义的类型,它必须成为抽象类的一个子类。任何一个类,只要它定义了所有要求的方法,并且遵守通用约定,则它就允许实现一个接口。
  • java只允许单继承,抽象类作为类型定义受到了极大的限制。

  看一下接口:


  1.   已有的类可以很容易被更新,以实现新的接口。只要:增加要求的方法,在类的声明上增加implements子句。
  2.   接口是定义mixin(混合类型)的理想选择。一个mixin是指这样的类型:一个类除了实现它的基本类型(primary type)之外,还可以实现这个mixin类型,以表明它提供了某些可选择的行为。接口之所以能定义mixin,因为它允许可选的功能可被混合到一个类型的基本类型中,而抽象类不能用于定义mixin类型,同样的理由是因为它们不能被更新到已有的类中:一个类不可能有一个以上的父类,并且在类层次结构中没有适当的地方来放置mixin。
      接口使得我们可以构造出非层次结构的类型结构。看例子:
    public interface Singer{
    AudioClip sing(Song s);
    }
    public interface Songwriter{
    Song compose(boolean hit);
    }
      为解决歌唱家本人也能做曲的情况,可以很简单地做到:
    public interface SingerSongwriter extends Singer, Songwriter{
    AudioClip strum();
    void actSensitive();
    }
      如果用抽象类来做,会是如何?
  3.   接口使得安全地增强一类的功能成为可能,做法是使用第14条介绍的包装类模式。如果用抽象类型来做,则程序员除了使用继承没有别的方法。
  4.   接口不允许包含方法的实现。把接口和抽象类的优点结合起来,对于期望导出的每一个重要接口,都提供一个抽象的骨架实现(skeletal implementaion)类。接口的作用仍是定义类型,骨架实现类负责所有与接口实现相关的工作。按照惯例,骨架实现被称为AbstractInterface(注:此interface是所实现的接口的名字,如AbstractList,AbstractSet)。看一个静态工厂:
    //List adapter for int array
    static List intArrayAsList(final int[] a){
    if (a==null) throw new NullPointerException();
    return new AbstractList(){
    public Object get(int i){
    return new Integer(a[i]);
    }
    public int size(){
    return a.length;
    }
    public Object set(int i,Object o){
    int oldVal=a[i];
    a[i]=((Integer)o).intValue();
    return new Integer(oldVal);
    }
    }
    }
      这个例子是一个Adapter,它使得一个int数组可以被看作一个Integer实例列表(由于存在int和Integer之间的转换,其性能不会非常好)
      骨架实现的优美之外在于,它们为抽象类提供了实现上的帮助,但又没有强加“抽象类被用做类型定义时候”所特有的严格限制。对一地一个接口的大多数实现来讲,扩展骨架实现类是一个很显然的选择,当然它也只是一个选择而已。
      实现了这个接口的类可以把对于接口方法的调用,转发到一个内部私有类的实例上,而这个内部私有类扩展了骨架实现类。这项技术被称为模拟多重继承。
      编写一个骨架实现类相对比较简单,首先要认真研究接口,并且确实哪些方法是最为基本的(primitive),其他的方法在实现的时候将以它们为基础,这些方法将是骨架实现类中的抽象方法;然后须为接口中的其它方法提供具体的实现。骨架实现类不是为了继承的目的而设计的。(怎么理解?)看例子:
    //skeletal implementation
    public abstract class AbstractMapEntry implements Map.Entry{
    //primitives
    public abstract Object getKey();
    public abstract Object getValue();

    //Entries in modifiable maps must override this method
    public Object setValue(Object value){
    throw new UnsupportedOperationException();
    }
    //Implements the general contract of Map.Entry.equals
    public boolean equals(Object o){
    if (o==this) return true;
    if (!(o instanceof Map.Entry)) return false;
    Map.Entry arg=(Map.Entry)o;
    return eq(getKey(),arg.getKey())&&eq(getValue(),arg.getValue());
    }
    private static boolean eq(Object o1,Object o2){
    return (o1==null?02==null:o1.equals(o2));
    }
    //implements the general contract of Map.Entry.hashCode
    public int hashCode(){
    return (getKey()==null?0:getKey.hashCode())^(getValue()==null?0:getValue().hashCode());
    }
    }

  使用抽象类来定义允许多个实现的类型,比使用接口有一个明显的优势:抽象类的演化比接口的演化要容易的多。在后续的发行版中,如果希望在抽象类中增加一个方法,只增加一个默认的合理的实现即可,抽象类的所有实现都自动提供了这个新的方法。对于接口,这是行不通的。虽然可以在骨架实现类中增加一方法的实现来解决部分问题,但这不能解决不从骨架实现类继承的接口实现的问题。由此,设计公有的接口要非常谨慎,一旦一个接口被公开且被广泛实现,对它进行修改将是不可能的。

Posted by Hilton at February 16, 2004 08:57 PM | TrackBack

posted on 2005-01-16 12:50  笨笨  阅读(1966)  评论(0)    收藏  举报

导航