打赏

何时使用接口(抽象类)?

1、接口的作用概述

接口的作用简单可以概括为:

1、避免客户端去实例化某个类

2、向上转型的使用——多态

3、扩展:Java中还能依靠接口实现多继承

2、接口的引进

先看这样一个场景:某个果园里现在有两种水果,一种是苹果,一种是香蕉,有客户想采摘园子里的水果,要求用 get() 方法表示,代码如下:

苹果类

public class Apple {
    public void get() {
        System.out.println("得到苹果");
    }
}

香蕉类

public class Banana {
    public void get() {
        System.out.println("得到香蕉");
    }
}

客户端

public static void one() {
        // 实例化一个apple
        Apple apple = new Apple();
        // 实例化一个banana
        Banana banana = new Banana();

        apple.get();
        banana.get();
}

苹果和香蕉各自维持一个属于自己的 get()方法,直接使用了 new 运算符进行实例化对象的操作,之后分别调用自己的 get()方法,中规中矩的实现过程,很好,我们完成了客户的任务。这时有了新的需求——需要用采摘到的水果做果汁,使用 doJuice(对应的水果) 方法表示,水果类的代码不变,客户端新加的其他代码如下:

    private static void doJuice(Apple apple) {
        apple.get();
        System.out.println("做成果汁");
    }

    private static void doJuice(Banana banana) {
        banana.get();
        System.out.println("做成果汁");
    }

客户端

public static void oneA() {
        // 实例化一个apple
        Apple apple = new Apple();
        // 实例化一个banana
        Banana banana = new Banana();

        doJuice(apple);
        doJuice(banana);
    }

任务完成,现在果园又引进了新品种,有橘子,西瓜,荔枝,葡萄,哈密瓜,火龙果,梨……

还是要采摘这些水果然后做果汁,如果还是用之前的代码实现,试想一下,除了必须加的新水果类之外,在客户端里还要为每一个水果类型分别添加对应的 doJuice(水果) 方法,然而水果种类那么多……少一个就不行,怎么改进呢?

为了增加程序的灵活性,引进接口。类图修改为:

做果汁的方法只需要一个即可,参数是接口类型:

    private static void doJuiceB(Fruit fruit) {
        fruit.get();
        System.out.println("做成果汁");
    }

客户端

    private static void two() {
        // 使用接口的引用指向子类的对象,向上转型过程,用到了多态
        Fruit apple = new AppleA();
        Fruit banana = new BananaA();
        Fruit orange = new OrangeA();

        doJuiceB(apple);
        doJuiceB(banana);
        doJuiceB(orange);
    }

事实上,想把各个水果都抽象化,可以选择抽象类或者接口去实现,而现在我们要创建不带任何方法定义和成员变量的抽象化的类,首选的应该是接口。

3、为什么要定义一个接口?

接口不仅仅是简单的抽象,它比对类型的抽象(抽象类)更进一步,是一种更纯粹的抽象过程。因为接口没有任何实现,没有任何和接口相关联的存储(PS,Java 8 对接口已经支持可以带实现的 default 方法),因此也就无法阻止多个接口的组合(多继承)。

前面例子里,抽象的其实更是对采集水果这个动作的抽象,用接口表示最好不过。客户端里使用了多态机制,可以把任何一个水果类对象当参数传入到 doJuice 方法里,基于这个设计,使得 Java 程序员不用再为类似的场景做出多余的努力,这就是使用接口的核心原因之一。因为这会使得程序变得非常灵活,而且通过继承还能对接口进行扩展——旧的接口去 extends 新的接口。

使用接口的另一个原因是和抽象类类似——避免某个类被实例化,告诉编译器和程序员,这个类不需要实例化,我们只是为了对某些行为做出规范,大家想用就去遵守这个规则。基于此,有的编程语言(比如 iOS 开发的 Objective-C)已经不把接口叫 interface,直接叫 protocol。

统一标准的目的是让大家都知道这个是做什么,但是不用知道怎么做,故这个类型不需要客户端去实例化,这一点和抽象类是一致的,更通俗的说接口就是个招牌。

比如说看到这个图标:

一看就知道这是 7-11 便利店,这个图标就是接口,人们远远的看到了这个接口,就知道这个店是 7-11 便利店(实现接口)。当然,这个店也可以不挂 7-11 的牌子,直接卖东西(直接写实现方法),那样我们就不能一看见店铺门,就简单粗暴的知道这个店铺是24小时便利商店……相反,需要采取如下做法:

1、沿着马路边走到每个店铺近前去观察,哪个是24小时营业的?(这就是反射的实现),很显然这样一家家的问实在是非常麻烦(一般情况下,反射不如直接 new 来的直接)……

2、要么我们就记住,xxx路xxx号店铺是24小时营业的便利店,离着它 200 米的 xxxx 号也是(这就是硬编码实现),很显然这样我们要记住的很多东西(代码量剧增),而且如果有新的便利店开张,我们也不可能知道(不利于扩展)……

而实现了接口,就意味着:店铺门前挂了这个招牌,我们不用进去问,甚至不用走到店铺门前,远远看一眼,看到了这个标志,就知道这个店铺是 7-11 便利店,24小时营业的

再举一个类似的例子,大家都知道吉祥馄饨。

接口也可以比喻为吉祥馄饨的连锁招牌,每个连锁店都有一样的馄饨菜单,一样的馄饨做法,一样的总部的馄饨配料……但是每个店铺实现的味道,每个店铺实现过程中的卫生情况……大家就不知道了,只能直观的,或者从网上平台去看各个店铺的口碑(具体类对接口的不同实现)。

同样对上一个例子—7-11便利店来说,人们不需要去具体询问店员你们的店铺是24小时营业的便利店么?人们只需要,也只能了解到 7-11 这个牌子代表的意思就足够了……映射到程序里,就是对具体的类来说,每一个方法是怎么实现的,调用者不知道,其实也不在乎。调用者它只需要知道接口的知识,也最多只能知道接口的知识,因此这是一个很好的抽象过程,和把不同层次的工作内容快速分离的过程。

4、项目里即使只有一个类使用接口,也不厌其烦的定义接口,且从业务上看,未来也不太可能有其他类用这个接口,那定义这个接口意义在哪里?

三个字:没卵用。一个好的系统是一步步的重构,优化得到的,而不是开始就设计出来的,避免早期的过度优化!

《Thinking in Java》一书说到“确定接口是理想选择,因而应该总是选择接口而不是具体的类,这其实是一个诱惑!因为对于创建类,几乎任何时候,都可以创建接口来代替!许多人都陷入了这个陷阱,在编写代码的时候,只要有那么一丝可能就去肆无忌惮的创建接口……貌似是因为需要使用不同的具体实现,实际上已经是过度优化,已经变成了一种草率的设计!任何抽象都应该以真正的需求出发。必要的时候,应该是重构接口而不是到处添加额外级别的间接性,并因此带来额外的复杂性,恰当的原则应该是:优先想到使用类,从类开始,如果接口的设计需求变得非常明确了,好的,进行重构吧!记住,接口虽然是一个很重要的工具,但是极其容易被滥用!”

5、抽象类能代替接口么?

有人说:我觉得抽象类完全可以替代接口,接口能做的抽象类都可以,而且抽象类还能包括实现。这比接口更强大……

到这里,先打住这个提问,别忘了 Java 不支持多继承!如果只是使用抽象类则必须继承 abstract class,而 Java 只允许单继承,所以仅在这一点上,接口存在就已经十分有意义了,它解决了 Java 不允许多继承的问题。

6、接口和抽象类有什么区别,选择使用接口和抽象类的依据是什么?

接口和抽象类的概念不一样。

接口是对动作的抽象,抽象类是对类型的抽象。

抽象类表示这个对象是什么,接口表示这个对象能做什么……

比如,人分男人,女人这两个类,他们的抽象类是人,这种继承关系说明他们都是人,而人都可以吃东西,然而狗也可以吃东西,我们就可以把“吃东西”这个动作定义成一个接口,然后其他需要的类去实现它,从而具备吃的行为。

所以,在高级语言上合理的继承设计就是:一个类只能继承一个类(抽象类,正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭、走路),这点是c++的缺陷。总结来说:

第一点. 接口是抽象类的更高一级的抽象,接口没有任何相关自身的存储,接口中所有的方法默认都是抽象的,默认都是public的,而抽象类只是声明方法的存在而不去实现它的类,需要public方法,则需要程序员指定访问权限

第二点. 接口可以多继承,抽象类不行

第三点. 接口定义方法,不能实现(Java8以后,接口也可以有default实现方法了),而抽象类可以实现部分方法,也就是抽象类可以同时存在普通方法和抽象方法

第四点. 接口中基本数据类型默认为 public static, 而抽类象不是的。

 一句话区分:当你关注一个事物的本质的时候,可以抽象共同的部分,做为抽象类;当你关注一个操作的时候,用接口。

7、接口在开发过程中可以快速分离工作内容

上层业务的开发者在写顾客购买商品需要支付这个业务逻辑的时候需要一个功能,就是连接数据库去访问顾客的商城钱包,但是他的工作专注于实现业务逻辑,不想分开精力去做底层实现,那么他只需要先定义一个接口,然后就可以继续他的业务逻辑代码了,而底层业务(算法,数据库连接,数据存储等)实现者可以根据这个接口,做他自己具体的实现,上层调用者不需要也不应该去知道底层实现,他只需要了解到接口这一级别即可。这样通过使用接口就可以快速的分离工作内容,达到团队并行工作的目的。

此外,如果规范是通过接口定义的,那么当你这个功能有多个实现时,你只要实现了这个接口,那么可以快速的替换具体实现,做到代码层面的完全分离。

总结起来就一句话:接口或者规范可以在开发过程中做到工作内容的分离。

团队的 A 写接口,B 写实现,C 写实现……B、C就不用写接口,B、C 看到 A 的接口就知道我要具体去实现什么功能,供上层调用者 A 使用即可,而对于 A 来说,不给你们统一规定好了,你们怎么知道该如何去实现哪些具体内容……

比如同样是登陆操作,A 不统一规定为 login(xxx); 那么很有可能 C 写一套登录实现叫 loginA,B 写一套登录实现叫 denglu……具体到程序里就会让人困惑……且无法快速的替换不同的实现过程。

更进一步,一个任务,A作为上层调用者,它需要一个算法方面的功能,但是 A 不需要去具体学习相关算法和实现,于是 A 就去写一个接口,而让 B 这个底层开发人员写实现,但是 B 恰恰今天不在公司,A 明天要出差,任务后天就交工,那 A 今天必须把使用这个接口的代码写好,A 写好接口,明天 B 把接口实现传个实例进来,任务 ok 交工。

故 interface 换个叫法就是 contract,有点合同的意思。B实现了这个接口,代表 B 承诺能做某些事情。A 需要一些能做某些事情的东西,于是 A 要求,必须实现了接口,才能被我调用。实际上也就是个“规范”。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

 

posted @ 2019-01-30 22:56  dashuai的博客  阅读(892)  评论(0编辑  收藏  举报
Flag Counter欢迎关注微信公众号