如何理解面向接口编程

2020-11-19 00:02发布

14条回答
爱梦 - 拿来吧你
2楼 · 2020-11-19 09:29

  在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。


Mantra
3楼 · 2020-11-19 09:34

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

小猪仔
4楼 · 2020-11-19 09:35

“面向”这个词,在软件设计编程中得到大量的使用。但似乎我们有不是很明白它到底代表什么意思。就像前面纠结“面向对象”这个词是什么意思的时候,我们也需要对“面向接口”这个词有较为深刻的解析。

“对象”与“数据”的区别在于“对象是信息与处理信息的方法的载体,而数据只是信息的载体”,而“面向”这个词的关键含义是,当我们在设计一个系统、实现一个系统的时候,我们基于、以什么为设计和编程的目标。面向过程设计和编程,设计师和程序员在设计和编程的时候所面对的、和所能够利用的东西往往只是一些变量。而面向对象则不同,他们面对的是一些对象,可以说是一些活生生的对象。

比如你要编程控制一个人。

面向过程编程中,给你的只是一个人的信息体(结构体),这个信息体中包含完整描述一个人的各种“静态信息”的数据,例如姓名、性别、年龄、体重、样貌等等,这种时候,如果要控制一个人,让他说一句话“你好吗”。那么你就需要,专门设计一个函数或者一个过程,来调用这个人的所具备的各种机体,然后让他说出话来(实际上是等于知道一个人如果把那句话发出来)。这是一个很让人头痛的东西,而我们希望的是程序员或这是设计人员面向的是一个活生生的人,这个人是会说话的,它并不需要我们叫他如何和说话,我们要让它说话只需要将说的内容传递给他,而他就会说出来。这个说话就是这个人的“动态信息”。

面向对象,就是提供给程序员一个完整的包括静态信息和动态信息的对象。让程序员不必花时间在该个体的某些动作的实现过程上。就像一个动物饲养员一样,不同的动物他们自己吃饭有不同的吃饭,动物饲养员只需要给予不同的动物不同的食物就可以了,前提是这个动物是有自主吃这个动作的。这就是面向对象最基本的概念——提供给设计师或程序员的是一个对象,一个包含那个个体的静态(数据)与动态(处理数据的方法)信息的整体。


kitidog2016
5楼 · 2020-11-19 10:30

了摆脱新手的概念,我这里也尽量不用main方法,而采用testNG编写测试用例。

定义:现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与U盘、MP3、移动硬盘等设备进行数据交换。

上下文(环境):已知要实现U盘、MP3播放器、移动硬盘三种移动存储设备,要求计算机能同这三种设备进行数据交换,并且以后可能会有新的第三方的移动存储设备,所以计算机必须有扩展性,能与目前未知而以后可能会出现的存储设备进行数据交换。各个存储设备间读、写的实现方法不同,U盘和移动硬盘只有这两个方法,MP3Player还有一个PlayMusic方法。

名词定义:数据交换={读,写}

解决方案列举

方案一:分别定义FlashDisk、MP3Player、MobileHardDisk三个类,实现各自的Read和Write方法。然后在Computer类中实例化上述三个类,为每个类分别写读、写方法。例如,为FlashDisk写ReadFromFlashDisk、WriteToFlashDisk两个方法。总共六个方法。

方案二:定义抽象类MobileStorage,在里面写虚方法Read和Write,三个存储设备继承此抽象类,并重写Read和Write方法。Computer类中包含一个类型为MobileStorage的成员变量,并为其编写get/set器,这样Computer中只需要两个方法:ReadData和WriteData,并通过多态性实现不同移动设备的读写。

方案三:与方案二基本相同,只是不定义抽象类,而是定义接口IMobileStorage,移动存储器类实现此接口。Computer中通过依赖接口IMobileStorage实现多态性。

方案四:定义接口IReadable和IWritable,两个接口分别只包含Read和Write,然后定义接口IMobileStorage接口继承自IReadable和IWritable,剩下的实现与方案三相同。

下面,我们来分析一下以上四种方案:

  首先,方案一最直白,实现起来最简单,但是它有一个致命的弱点:可扩展性差。或者说,不符合“开放-关闭原则”(注:意为对扩展开放,对修改关闭)。当将来有了第三方扩展移动存储设备时,必须对Computer进行修改。这就如在一个真实的计算机上,为每一种移动存储设备实现一个不同的插口、并分别有各自的驱动程序。当有了一种新的移动存储设备后,我们就要将计算机大卸八块,然后增加一个新的插口,在编写一套针对此新设备的驱动程序。这种设计显然不可取。

  此方案的另一个缺点在于,冗余代码多。如果有100种移动存储,那我们的Computer中岂不是要至少写200个方法,这是不能接受的!

  再看 方案二和方案三,之所以将这两个方案放在一起讨论,是因为他们基本是一个方案(从思想层面上来说),只不过实现手段不同,一个是使用了抽象类,一个是使用了接口,而且最终达到的目的应该是一样的。

  我们先来评价这种方案:首先它解决了代码冗余的问题,因为可以动态替换移动设备,并且都实现了共同的接口,所以不管有多少种移动设备,只要一个Read方法和一个Write方法,多态性就帮我们解决问题了。而对第一个问题,由于可以运行时动态替换,而不必将移动存储类硬编码在Computer中,所以有了新的第三方设备,完全可以替换进去运行。这就是所谓的“依赖接口,而不是依赖与具体类”,不信你看看,Computer类只有一个MobileStorage类型或IMobileStorage类型的成员变量,至于这个变量具体是什么类型,它并不知道,这取决于我们在运行时给这个变量的赋值。如此一来,Computer和移动存储器类的耦合度大大下降。

  那么 这里该选抽象类还是接口呢?还记得第一篇文章我对抽象类和接口选择的建议吗?看动机。这里,我们的动机显然是实现多态性而不是为了代码复用,所以当然要用接口。

  最后 我们再来看一看方案四,它和方案三很类似,只是将“可读”和“可写”两个规则分别抽象成了接口,然后让IMobileStorage再继承它们。这样做,显然进一步提高了灵活性,但是,这有没有设计过度的嫌疑呢?我的观点是:这要看具体情况。如果我们的应用中可能会出现一些类,这些类只实现读方法或只实现写方法,如只读光盘,那么这样做也是可以的。如果我们知道以后出现的东西都是能读又能写的,那这两个接口就没有必要了。其实如果将只读设备的Write方法留空或抛出异常,也可以不要这两个接口。总之一句话:理论是死的,人是活的,一切从现实需要来,防止设计不足,也要防止设计过度。

  在这里,我们姑且认为以后的移动存储都是能读又能写的,所以我们选方案三

 

实现

下面,我们要将解决方案加以实现。我选择的语言是Java,所以使用其他语言的朋友一样可以参考。

首先编写IMobileStorage接口:

Code:IMobileStorage

1 public interface IMobileStorage {2 3     void Read();                    // 读取数据4     void Write();                   // 写入数据5 6 }

  代码比较简单,只有两个方法,没什么好说的,接下来是三个移动存储设备的具体实现代码:

 

U盘

Code:FlashDisk

复制代码

 1 public class FlashDisk implements IMobileStorage{ 2     @Override 3     public void Read() { 4         System.out.println("Reading from FlashDisk……"); 5         System.out.println("Read finished!"); 6     } 7  8     @Override 9     public void Write() {10         System.out.println("Writing to FlashDisk……");11         System.out.println("Write finished!");12     }13 }

复制代码

MP3

Code:MP3Player

复制代码

public class MP3Player implements IMobileStorage{
    @Override    public void Read() {
        System.out.println("Reading from MP3Player……");
        System.out.println("Read finished!");
    }

    @Override    public void Write() {
        System.out.println("Writing to MP3Player……");
        System.out.println("Write finished!");
    }    public void PlayMusic(){
        System.out.println("Music is playing……");
    }
}

复制代码

移动硬盘

Code:MobileHardDisk

复制代码

public class MobileHardDisk implements IMobileStorage{

    @Override    public void Read() {
        System.out.println("Reading from MobileHardDisk……");
        System.out.println("Read finished!");
    }

    @Override    public void Write() {
        System.out.println("Writing to MobileHardDisk……");
        System.out.println("Write finished!");
    }

}

复制代码

  可以看到,它们都实现了IMobileStorage接口,并重写了各自不同的Read和Write方法。下面,我们来写Computer:

Code:Computer

复制代码

public class Computer {    private IMobileStorage _usbDrive;    public IMobileStorage get_usbDrive() {        return _usbDrive;
    }    public void set_usbDrive(IMobileStorage _usbDrive) {        this._usbDrive = _usbDrive;
    }    public Computer(){}    public Computer(IMobileStorage _usbDrive) {        this._usbDrive = _usbDrive;
    }    public void ReadData(){        this._usbDrive.Read();
    }    public void WriteData(){        this._usbDrive.Write();
    }
}

复制代码

  其中的UsbDrive就是可替换的移动存储设备,之所以用这个名字,是为了让大家觉得直观,就像我们平常使用电脑上的USB插口插拔设备一样。

OK!下面我们来测试我们的“电脑”和“移动存储设备”是否工作正常。我是用的Java控制台程序打印结果,具体代码如下:

Code:测试代码

复制代码

public class ToTest {
    @Test    public void program1(){
        Computer computer = new Computer();
        IMobileStorage mp3Player = new MP3Player();
        IMobileStorage flashDisk = new FlashDisk();
        IMobileStorage moblieHardDisk = new MobileHardDisk();

        System.out.println("I inserted my MP3 Player into my computer and copy some music to it:");
        computer.set_usbDrive(mp3Player);
        computer.WriteData();
        System.out.println("====================");

        System.out.println("Well,I also want to copy a great movie to my computer from a mobile hard disk:");
        computer.set_usbDrive(moblieHardDisk);
        computer.ReadData();
        System.out.println("====================");

        System.out.println("OK!I have to read some files from my flash disk and copy another file to it:");
        computer.set_usbDrive(flashDisk);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

复制代码

 

运行结果如下:

图2.1 各种移动存储设备测试结果

              图2.1 各种移动存储设备测试结果

 

  好的,看来我们的系统工作良好。

  后来……

 

  刚过了一个星期,就有人送来了新的移动存储设备NewMobileStorage,让我测试能不能用,我微微一笑,心想这不是小菜一碟,让我们看看面向接口编程的威力吧!将测试程序修改成如下:

 (NewMobileStorage的类请参照u盘、移动硬盘等类编写……也可以自创)

测试代码

复制代码

    @Test    public void program2(){
        Computer computer = new Computer();
        IMobileStorage newMobileStorage = new NewMoblieStorage();
        computer.set_usbDrive(newMobileStorage);
        newMobileStorage.Write();
        newMobileStorage.Read();

    }

复制代码

运行结果:

              图2.2 新设备扩展测试结果

 

  又过了几天,有人通知我说又有一个叫SuperStorage的移动设备要接到我们的Computer上,我心想来吧,管你是“超级存储”还是“特级存储”,我的“面向接口编程大法”把你们统统搞定。

  但是,当设备真的送来,我傻眼了,开发这个新设备的团队没有拿到我们的IMobileStorage接口,自然也没有遵照这个约定。这个设备的读、写方法不叫Read和Write,而是叫rd和wt,这下完了……不符合接口啊,插不上。但是,不要着急,我们回到现实来找找解决的办法。我们一起想想:如果你的Computer上只有USB接口,而有人拿来一个PS/2的鼠标要插上用,你该怎么办?想起来了吧,是不是有一种叫“PS/2-USB”转换器的东西?也叫适配器,可以进行不同接口的转换。对了!程序中也有转换器。

  这里,我要引入一个设计模式,叫“Adapter”。它的作用就如现实中的适配器一样,把接口不一致的两个插件接合起来。由于本篇不是讲设计模式的,而且Adapter设计模式很好理解,所以我就不细讲了,先来看我设计的类图吧:

  如图所示,虽然SuperStorage没有实现IMobileStorage,但我们定义了一个实现IMobileStorage的SuperStorageAdapter,它聚合了一个SuperStorage,并将rd和wt适配为Read和Write,SuperStorageAdapter(这里注意自行编写SuperStorage的类和他用到的接口

            图2.3 Adapter模式应用示意

  

具体代码如下:

Code:SuperStorageAdapter

复制代码

 1 public class SuperStorageAdapter implements IMobileStorage { 2     private SuperStorage _superStorage; 3  4     public SuperStorage get_superStorage() { 5         return _superStorage; 6     } 7  8     public void set_superStorage(SuperStorage _superStorage) { 9         this._superStorage = _superStorage;10     }11 12     @Override13     public void Read(){14         this._superStorage.rd();15     }16 17     @Override18     public void Write() {19         this._superStorage.wt();20     }21 }

复制代码

好,现在我们来测试适配过的新设备,测试代码如下:

Code:测试代码

复制代码

  @Test    public void program3(){
        Computer computer = new Computer();
        SuperStorageAdapter superStorageAdapter = new SuperStorageAdapter();
        SuperStorage superStorage = new SuperStorage();
        superStorageAdapter.set_superStorage(superStorage);

        System.out.println("Now,I am testing the new super storage with adapter:");
        computer.set_usbDrive(superStorageAdapter);
        computer.ReadData();
        computer.WriteData();
        System.out.println();
    }

复制代码

运行结果:

          图2.4 利用Adapter模式运行新设备测试结果

 

 


魏魏姐
6楼 · 2020-11-19 10:33

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

三岁奶猫
7楼 · 2020-11-19 11:03

个人理解,面向接口编程则是站在了程序实现过程中,需要依赖某个对象时的抽象过程。比如:你人生中需要找一个男朋友,但是在茫茫人海里,你并不知道具体是谁。所以你可以事先规划你的人生,预留一个男人的接口就可以了。具体到时候谁和你结婚生子,你可以先不用关心。也许这个例子并不是很贴切,我们换一个例子,比如:你要吃肉,然后你就出钱给猎人去打猎。猎人去山里,打到猪你就吃猪,打到狗你就吃狗。对于你来说你只是关心吃肉,但是不同的猎人去打猎,你就具备了不同的能力(吃猪肉和吃狗肉)。

赵小刀
8楼 · 2020-11-19 14:45

不知道怎么给你说清楚

形象一点,c语言是面向过程编程,就是按程序执行的顺序编

而c++和java都是面向对象编程,他们把变量和方法都封装到类里面,通过对象执行程序

但是c++中的类是可以继承的,而JAVA的不能继承,只能通过实现接口来完成类似于c++继承的功能,从这个角度看java是面向接口编程


在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

相关问题推荐

  • 回答 2

    Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpda...

  • 回答 22

    忙的时候项目期肯定要加班 但是每天加班应该还不至于

  • 回答 108
    已采纳

    虽然Java人才越来越多,但是人才缺口也是很大的,我国对JAVA工程师的需求是所有软件工程师当中需求大的,达到全部需求量的60%-70%,所以Java市场在短时间内不可能饱和。其次,Java市场不断变化,人才需求也会不断增加。马云说过,未来的制造业要的不是石油,...

  • 回答 5
    已采纳

    工信部证书含金量较高。工信部是国务院的下属结构,具有发放资质、证书的资格。其所发放的证书具有较强的权威性,在全国范围内收到认可,含金量通常都比较高。 工信部证书,其含义也就是工信部颁发并承认的某项技能证书,是具有法律效力的,并且是国家认可的...

  • 回答 70
    已采纳

    学Java好不好找工作?看学完Java后能做些什么吧。一、大数据技术Hadoop以及其他大数据处理技术都是用Java或者其他,例如Apache的基于Java 的 HBase和Accumulo以及ElasticSearchas。但是Java在此领域并未占太大空间,但只要Hadoop和ElasticSearchas能够成长壮...

  • 回答 16
    已采纳

    就是java的基础知识啊,比如Java 集合框架;Java 多线程;线程的五种状态;Java 虚拟机;MySQL (InnoDB);Spring 相关;计算机网络;MQ 消息队列诸如此类

  • 回答 12

    #{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础,总体上他们的作用是一致的(为了动态传参),但是在编译过程、是否自动加单引号、安全性、使用场景等方面有很多不同,下面详细比较两者间的区别:1.#{} 是 占位符 :动态解析 ...

  • 回答 62

    没问题的,专科学历也能学习Java开发的,主要看自己感不感兴趣,只要认真学,市面上的培训机构不少都是零基础课程,能跟得上,或是自己先找些资料学习一下。

  • 回答 4

    1、反射对单例模式的破坏采用反射的方式另辟蹊径实例了该类,导致程序中会存在不止一个实例。解决方案其思想就是采用一个全局变量,来标记是否已经实例化过了,如果已经实例化过了,第 二次实例化的时候,抛出异常2、clone()对单例模式的破坏当需要实现单例的...

  • 回答 5

     优点: 一、实例控制  单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 二、灵活性  因为类控制了实例化过程,所以类可以灵活更改实例化过程。 缺点: 一、开销  虽然数量很少,但如果每次对象请求引用时都要...

  • 回答 4

    这个主要是看你数组的长度是多少, 比如之前写过的一个程序有个数组存的是各个客户端的ip地址:string clientIp[4]={XXX, xxx, xxx, xxx};这个时候如果想把hash值对应到上面四个地址的话,就应该对4取余,这个时候p就应该为4...

  • 回答 6

     哈希表的大小 · 关键字的分布情况 · 记录的查找频率 1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。...

  • 回答 6

    哈希表的大小取决于一组质数,原因是在hash函数中,你要用这些质数来做模运算(%)。而分析发现,如果不是用质数来做模运算的话,很多生活中的数据分布,会集中在某些点上。所以这里最后采用了质数做模的除数。 因为用质数做了模的除数,自然存储空间的大小也用质数了...

  • 回答 2

    是啊,哈希函数的设计至关重要,好的哈希函数会尽可能地保证计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间

  • 回答 3

     解码查表优化算法,seo优化

  • 回答 5

    1.对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。2.哈希值就是这个元素的位置。3.如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就...

没有解决我的问题,去提问