第18条:接口优于抽象类

java中可以用来定义允许多个实现的类型有两种:接口和抽象类。

接口和抽象类的区别:

1,抽象类中可以存在某些方法的实现,接口不可以

2,如果要实现抽象类定义的类型,类必须成为抽象类的子类。而对接口来说,任何一个类,只要实现接口里面必要的方法,就可以了,而且不用管这个类处于类的层次的哪个位置(例如:内部类也可以实现)

3,java是单继承,多实现

现有的类可以很容易的被更新,以实现接口的形式。例如,现在有多个类要扩展排序功能,而咱们要做的就是对这些类实现comparable接口,并且重新接口中必要的compare方法就可以了。但是如果要用抽象类的形式扩展的话,那么就必须对抽象类所有的子类进行扩展。

接口是定义mixin(混合类型)的理想选择。Comparable这样的接口被称为mixin的原因是:它允许任何的功能被混合到类型的主要功能中(排序功能)。抽象类并不能用于定义mixin,原因在于它们不能被更新到现有类中:java是单继承,多实现 所以类不可能有一个以上的父类,类层次结构中也没有合适的地方来插入mixin,问题在于功能是可选的,如果把一个功能放入类层次中,那就将这项功能混入到了所有的子类中。

例如:我们要实现以下4种动物:

Dog - 狗狗;
Bat - 蝙蝠;
Parrot - 鹦鹉;
Ostrich - 鸵鸟。
如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次:

animal  -  m      b

但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次:

animal-r     f

如果要把上面的两种分类都包含进来,我们就得设计更多的层次:

哺乳类:能跑的哺乳类,能飞的哺乳类;
鸟类:能跑的鸟类,能飞的鸟类。
这么一来,类的层次就复杂了:

animal-mb-rf

如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。

正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:

class Animal(object):
pass

抽象类:

class Mammal(Animal):
pass

class Bird(Animal):
pass

各种动物:

class Dog(Mammal):
pass

class Bat(Mammal):
pass

class Parrot(Bird):
pass

class Ostrich(Bird):
pass
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类:

class Runnable(object):
def run(self):
print(‘Running…’)

class Flyable(object):
def fly(self):
print(‘Flying…’)
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal, Runnable):
pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:

class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。

Mixin
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。

而此处如果咱们不用多继承,那么咱们可以把跑和飞分别定义为一个接口,咱们只要实现接口就可以了

 接口允许我们构造非层次结构的类型框架。类型层次对于组织某些事物是非常合适的,但有些事物并不能被整齐地组织成一个严格的层次结构。例如,有一个接口代表singer,一个接口代表songwriter,这两者之间可能并不存在父子关系,在现实中有些歌唱家本身也是作曲家。如果我们使用接口来定义这些类型,对于单个类而言,它同时实现Singer和songwriter是完成可以的,实际上,甚至可以定义第三个接口,让它同时扩展singer和songwriter,并且可以添加一些新的方法。

public interface Singer {  

    AudioClip sing(Song s);  

}  

public interface Songwriter {  

    Song compose(boolean hit);  

}    

public interface SingerSongWriter extends Singer, Songwriter {  

    AudioClip strum();  

    void actSensitive();  

}  

类似这种情况,如果咱们用创建层次类来实现的话,那么咱们每种情况都要创建一个类,就会导致组合臃肿


第16条中介绍的包装类模式,接口便利安全地增加类的功能成为可能。如果使用抽象类来定义类型,那么程序员除了使用继承的手段来增加新的功能以外没有其他的选择。这样得到的类与包装类相比,功能更差,更脆弱。

        虽然接口不允许包含方法的实现,但是我们可以通过对导出的每个重要的接口都提供一个抽象的骨架实现类,这样咱们可以把接口和抽象类的优点结合起来。接口的作用仍然是定义类型,骨架实现类接管了所有与接口实现相关的工作。按照惯例,骨架实现被称为 AbstractInterface,这里的Interface是指所实现的接口的名子。如果设计得当,骨架实现可以使我们可以很容易提供自己的接口实现。例如,下面是一个静态工厂方法,它包含一个完整的,功能全面的List实现。

// Concrete implementation built atop skeletal implementation  

static List<Integer> intArrayAsList(final int[] a) {  

    if (a == null)  

        throw new NullPointerException();  

    return new AbstractList<Integer>() {  

        public Integer get(int i) {  

            return a[i];  

        }  

        @Override  

        public Integer set(int i, Integer val) {  

            int oldVal = a[i];  

            a[i] = val;  

            return oldVal;  

        }  

        public int size() {  

            return a.length;  

        }  

    };  

}  

        上面的代码中利用了骨架实现AbstractList来实现一个完整的List,实际上,骨架是做为一个被匿名内部类的扩展类存在的,在这个内部类中对骨架实现新增加了两个方法并重写了一个方法以定制我们所需要的List的功能。由于骨架实现本身已经实现了很多通用的操作,在这里实际上只做了很少的改动就得到了一个功能良好的类。

1 :定义一个基本的接口,其中有c ,d,f 三种方法

public interface Mydemo{

public void c();

public void d();

public void f(); 

};

2:定义一个抽象类,其中有两个方法a和b,实现接口并实现c和d和 f

public abstract class AbrMydemo implements Mydemo{

public void a();

public void b();

public void c(){

}

public void d(){

}

public void f(){ 

}

};

public class maindemo extends AbrMydemo{//无需改变,继承父类实现的方法,可以直接调用

public void a(){

}

public void b(){

}

//继承AbrBase对IBase的实现

};

也可以直接用匿名内部类的方式转发AbrMydemo中的方法,在maindemo中使用

public class maindemo {

public Mydemoa(){

Mydemo  demo = new AbrMydemo()

demo.f();

}

public class Nbl(){

也可以在内部类中直接调用外部类的方法

}

public void b(){

}

//继承AbrBase对IBase的实现

};

        骨架实现的美妙之处在于,它们为抽象类提供了实现上的帮助,又不强加抽象类作为类型定义时的限制,可以扩展骨架实现也完全可以手动实现整个接口。此外骨架实现类也有助于接口的实现。实现了这个接口的类可以把对于接口方法的调用,转发到另一个内部私有类的实例上,这个内部私有类扩展了骨架类的实现这种方法被称为“模拟多重继承”,这项技术具有多重继承的绝大多数优点,同时又避开了相应的缺陷,实际上就是用包装与转发模拟了多个类的混合功能。


编写骨架实现类相对比较简单,但是有点单调乏味,首先,必须认真研究接口,并确定哪些方法是最为基本的,其他的方法则可以根据它们来实现。这些基本的方法将成为骨架实现类中的抽象方法。然后,必须为接口中所有其他的方法提供具体实现。例如接口Map与骨架实现AbstractMap。由于骨架实现是为了继承的目的设计的,所以应该遵守第17条中介绍的所有关于设计和文档的指导原则。

        骨架实现上有个小小的不同,就是简单实现。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;  

        }  

    }  


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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,562评论 18 399
  • 1 场景问题# 1.1 发送提示消息## 考虑这样一个实际的业务功能:发送提示消息。基本上所有带业务流程处理的系统...
    七寸知架构阅读 4,951评论 5 63
  • 苏心2017阅读 282评论 0 1
  • plan 完成原型2.0的设计 ,要点已经清楚 如果还有精力, 腹肌撕裂者 + nike training
    santiago_liii阅读 112评论 0 0