《Effective java》读书记录-第18条-接口优于抽象

接口和抽象类的区别

1.抽象类允许包含某些方法的实现,但是接口不允许。

2.为了实现由抽象类定义的类型,类必须成为抽象类的一个子类;只要类定义了所有必要的方法,并且遵守通用约定,它就被允许实现一个接口。


现有的类可以很容易被更新,以实现新的接口。


接口是定义mixin(混合类型)的理想选择。



接口允许我们构造非层次结构的类型框架

现在有个接口代表歌手和作曲家

public interface Singer {
    AudioClip sing(Song s);
}
public interface Songwriter {
    Song compose(boolean hit);
}
现实的情况中,有些歌手本身也是作曲家。因为使用的是接口而不是抽象类,所以对于单个类而言,它可以同时实现Singer和Songwriter两个接口。

public interface SingerSonwriter extends  Singer,Songwriter {
    AudioClip strum();
    void actSensitive();
}

这种情况不是很常见,但却能解决大问题。

如果是采用层次结构的做法,那么每一个要被支持的类的属性组合,都要包含一个单独的类。如果整个类型系统有N个属性,那么就必须支持2ⁿ种可能的组合。这种现象称为“组合爆炸”。之所以会产生这种问题,是因为类层次中没有任何类型体现了公共的行为特征。

接口使得安全地增强类的功能成为可能(包装类模式)。如果使用抽象类,除了使用继承来增加功能,没有其他选择。与包装类相比功能更差、也更脆弱。

虽然接口不允许包含方法的实现,可以为每个重要接口都提供一个抽象的骨架实现(skeletal implementation)类,这样就把接口和抽象类的优点结合起来。接口的作用依然是定义类型,但是骨架实现类接管了所有与接口实现相关的工作。习惯把骨架实现称为AbstractInterface。

骨架实现的美妙之处在于,它们为抽象类提供了实现上的帮助,但又不强加“抽象类被用作类型定义时”所特有的限制。对于大多数接口来说,扩展骨架实现类不是必需的。


public abstract class AbstractMapEntry<K, V> implements Map.Entry<K, V> {
    //Primitive operations
    public abstract K getKey();

    public abstract V getValue();

    //Entries in modifiable maps must override this method
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    //Implements the general contract of Map.Entry.equals
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof Map.Entry)) return false;
        Map.Entry<?, ?> arg = (Map.Entry<?, ?>) obj;
        return equals(getKey(), arg.getKey())
                && equals(getValue(), arg.getValue());
    }

    private static boolean equals(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    //Implements the general contract of Map.Entry.equals
    @Override
    public int hashCode() {
        return hashCode(getKey()) ^ hashCode(getValue());
    }

    private static int hashCode(Object obj) {
        return obj == null ? 0 : obj.hashCode();
    }
}
因为骨架实现类是为了继承的目的而设计的,所以应该遵守 第17条的准则
与骨架实现有个小小不同的简单实现(simple implementation),看起来很像骨架实现,因为它实现了接口,并且是为继承而设计的,区别在于它不是抽象的:它是最简单的可能的有效实现。你可以原封不动的使用,也可以看情况将它子类化。AbstractMap.SimpleEntry就是这样的例子。

public static class SimpleEntry<K,V>
        implements Entry<K,V>, java.io.Serializable
    {
        private static final long serialVersionUID = -8499721149061103585L;

        private final K key;
        private V value;

        public SimpleEntry(K key, V value) {
            this.key   = key;
            this.value = value;
        }

        public SimpleEntry(Entry<? extends K, ? extends V> entry) {
            this.key   = entry.getKey();
            this.value = entry.getValue();
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            return eq(key, e.getKey()) && eq(value, e.getValue());
        }

        public int hashCode() {
            return (key   == null ? 0 :   key.hashCode()) ^
                   (value == null ? 0 : value.hashCode());
        }

        public String toString() {
            return key + "=" + value;
        }

    }

接口比抽象类优势很多,但是抽象类仍然有个明显的优势: 抽象类的演变比接口的演变要容易很多。如果在后续的版本中需要在抽象类中增加新的方法,那么建立一个具体方法后,就可以提供默认的实现,抽象类的所有实现都将提供这个新的方法。而接口,就不能这样做。

虽然接口有骨架实现类,在接口中增加方法后,再骨架实现类也增加具体的方法不就可以解决了。可是那些不从骨架实现类继承的接口实现仍然会遭到破坏。

因此,设计公有的接口要非常谨慎。接口一旦被公开发型,并且已被广泛实现,再想改变这个接口几乎是不可能的。在发行新接口的时候,最好的做法是,在接口被“冻结”之前,尽可能让更多的程序员用尽可能多的方式来实现这个新街口,这样有助于在依然可以改正缺陷的时候就发现它们。

简言之,接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,既当演变的容易性比灵活性和功能更为重要的时候(这种情况,应该用抽象类来定义类型)。如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。最后,应该尽可能谨慎地设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值