NO.14 你所知道的接口,不一定是最全的 | Java敲黑板系列

开场白

老铁 :通过前三篇文章,我们基本把Java对象“捏碎揉烂”个底朝天。后续将进入一个新的系列“接口与类”;今天我们将深入探讨接口的内涵与注意事项。

面向对象编程.VS.面向接口编程,何去何从?

我们常说“Java是一门面向对象编程的语言”,但是我们又常把“面向接口编程”挂在嘴边。一个是面向对象,一个是面向接口,到底哪一个才是正确的呢?突然间我们的思绪在风中变得凌乱。其实,这不是一个二选一的命题,不仅没有自相矛盾,而且还是自成体系、逻辑自洽。

面向对象与面向接口并不在一个层面说同一个事情。面向对象是一种编程思想体系;而面向接口是该体系中的重要组成部分,是为了更好地让面向对象落地而实施的一种技术方法。

接口是什么?

下面我们通过代码举例来说明什么是接口,先看代码1。

//用interface关键字声明接口
public interface IPhone {
    //定义一个常量
    public static final int SimCardCount=2;

    //以下为函数声明:只声明不实现
    public void dail();
    public void msg();
    public void email();
}

在代码1中,我们定义了一个接口IPhone(如有雷同,纯属巧合),在该代码中几乎可以包括接口的所有特点,如下所述。

敲黑板

  1. 接口用interface来进行标识。
  2. 在定义的接口中,只能声明若干函数与定义一些常量,不能包括任何函数的实现代码。
  3. interface成为一种“契约”,这个“契约”将由某个class进行实现(implements)。当实现某个interface时,该class必须为interface的所有函数提供代码实现。当然,如果一个class不能完全实现接口中所声明的方法时,那么该类必须定义为abstract class。
  4. 如果某个class实现了某个interface,该class object也将是那个interface的一个类型实例,要测试一个class是否实现了某个interface,可使用instanceof操作进行判断。

为什么要面向接口编程

我们先从生活中的例子说起,我们平常用的台式机电脑就是一个很好的例子,比如有一天我的电脑内存条坏了,那么我只需要去购买一只内存条,拔掉坏的内存条,在内存插槽口上插入新买的内存条就大功告成;如果我们把主板看成是一个对象、把内存看成是另外一个对象,那么主板上的插槽口就是一个“接口”,而内存条插入该插槽口,就可以理解为内存实现了该“接口”。

通过上面的生活实例,我们可以发现换内存条的过程不需要操作者掌握计算机内部运行原理和过程,也不需要去动主板、拆硬盘,只需要操作者正确找到内存插槽口即可;是不是有一种生活很美好的感觉油然而生?

回到代码世界,为什么我们要面向接口编程了? 在回答这个问题之前,我们首先要回答什么是面向接口编程?

采用面向对象语言实现的软件系统,总是包括了各种形形色色的对象,软件系统就是通过这些对象按照一定的逻辑开展相互协作完成特定的业务功能。可见,我们不仅要把对象的内部功能实现好,各个对象之间的协作关系也是软件设计实现的关键。对象与对象之间的交互协作采用接口来实现,通过接口我们就可以屏蔽各自对象内部修改的细节;只要交互的对象之间保持接口一致,对象内部的实现修改对协作对象是没有任何影响的;此外,当客户的需求发生变化,需要新增功能时,只需要编写新的类来实现该接口,完成新的业务功能即可,尽量做到少修改现有代码,将系统的影响降低到最低。

与“面向接口编程”相对的是“面向实例编程”,以下我们通过代码来说明一下两者的区别以及为什么我们需要“面向接口编程”。其中代码2为面向实现编程,代码3为面向接口编程。

代码2:

public class TestInterface {
    class OracleDriver{
        public void connect(){
            System.out.println("Oracle DB Connected!");
        }
    }

    class MysqlDriver{
        public void connect(){
            System.out.println("Mysql DB Connected!");
        }
    }

    class DataBase{
        //面向实现编程
        public void connnect(OracleDriver o){
            o.connect();
        }
    }

    //测试方法
    public void doConnect(){
        DataBase db = new DataBase();
        OracleDriver o = new OracleDriver();
        db.connnect(o);
        MysqlDriver m = new MysqlDriver();
        //以下语句去掉注释符后,将无法编译通过
        //db.connnect(m);
    }

    //测试客户端
    public static void main(String[] args){
        TestInterface impl = new TestInterface();
        impl.doConnect();
    }
}

代码3:

public class TestInterface2 {
    //将业务操作提取为一个接口
    interface IOperator{
        public void connect();
    }
    //实现业务接口
    class OracleDriver implements IOperator{
        public void connect(){
            System.out.println("Oracle DB Connected!");
        }
    }
    //实现业务接口
    class MysqlDriver implements IOperator{
        public void connect(){
            System.out.println("Mysql DB Connected!");
        }
    }

    class DataBase{
        //面向接口编程,传入的参数类型为接口
        public void connnect(IOperator o){
            o.connect();
        }
    }

    //测试方法
    public void doConnect(){
        DataBase db = new DataBase();
        OracleDriver o = new OracleDriver();
        db.connnect(o);
        MysqlDriver m = new MysqlDriver();
        //以下语句将顺利编译通过
        db.connnect(m);
    }

    //测试客户端
    public static void main(String[] args){
        TestInterface2 impl = new TestInterface2();
        impl.doConnect();
    }
}

代码2为面向实现编程,其主要问题表现在,业务函数connect传入的变量参数为具体对象类OracleDriver,这样直接导致了该方法只能接受OracleDriver类型,而不能接受MysqlDriver类型。

代码3位面向接口编程,其最大的变化是把业务操作提取成了一个接口IOperator,而业务对象均实现该接口,在业务函数connect传入的变量参数为接口类型IOperator,为此,在客户端调用时,该方法就能同时接受OracleDriver与MysqlDriver类型,大大提升了代码的可重用性。

此外,老铁们也可深入思考一下,从功能可扩展性角度来考虑一下上述哪个方案更好:比如目前只考虑了connect操作,后续需要增加close等操作;目前只考虑了Oracle、MySql,后续需要增加SqlServer等不同类型数据库等需求;欢迎老铁们将自己的想法写在留言区与大家共同交流。

接口的多重继承

在Java中,我们都知道class是不能继承多个class,而只能继承一个class;但是一个class却可以实现多个interface,其实也就相当于具有了多重继承的特性,如代码4所示。

代码4:

public class TestInterface2 {
    interface IMail{
        public void sendMail();
        public void recvMail();
    }

    interface IMsg{
        public void sendMsg();
        public void recvMsg();
    }

    //接口的多重继承特性 
    class Phone implements IMail,IMsg{
        @Override
        public void sendMsg() {}

        @Override
        public void recvMsg() {}

        @Override
        public void sendMail() {}

        @Override
        public void recvMail() {}
    }
}

但是,如果一个class同时实现了两个或两个以上的interface,而这些interface中存在函数名称或常量名称冲突的情况,则会出现“语义问题”:

  • 这些interface中存在函数名称相同,但是返回类型不同的情况;则编译无法通过;
  • 这些interface中函数名称相同,返回类型也相同;则会在语义上给人困扰,在实际编程中应该避免。解决方法也很简单,谨慎的为Interface中的函数与常数命名,尽量避免冲突。

转载自公众号:代码荣耀
图1

猜你喜欢

转载自blog.csdn.net/maijia0754/article/details/80578206
今日推荐