6.6 深入接口——通信双方的协议

img

扫码看视频

前面我们了解到了如何定义和使用接口,其实接口还有一个更重要的作用,那就是作为模块与模块之间通信的协议。

在软件领域,一直以来都希望能够实现像硬件生产一样,不同的零部件由不同的厂商生产,然后按照标准的接口进行组装,得到成品。以计算机为例,要组装一台计算机,我们需要主板、CPU、显卡、内存等配件,虽然这些配件是由不同厂家生产的,但这并不影响我们组装成一台计算机,我们只需要将这些配件插在主板的对应插槽中就可以了,因为主板生产商和其他配件生产商都会针对某个插槽定义的规范进行生产,而配件在主板上的这些插槽就类似于Java中的接口。

在大型软件系统中,通常都是多人协作开发,将整个系统进行拆解,划分出子系统和模块,然后分工协作,不同的开发人员负责不同的模块开发。而模块之间如何调用,则可以通过接口来约定,也就是说,接口可以作为模块与模块之间通信的协议。定义接口,相当于制定了模块之间通信的协议,两个模块要想通过接口进行通信,那么必然是一个模块实现了接口,提供了接口中声明的方法实现,而另一个模块则通过接口来调用其实现。

上面的内容比较抽象,下面我们通过一个计算机组装的例子来看看接口是如何作为通信双方的协议的。

首先,我们定义两个接口CPU和GraphicsCard,代表主板上的CPU和显卡接口。如代码6.17和代码6.18所示。

img
img

前面说了,通过接口通信的双方,必然有一方要实现接口,另一方通过接口来调用其实现。上面两个接口定义了CPU和显卡需要实现的方法,于是CPU厂商和显卡厂商根据各自的接口定义开始生产相应的产品,代码如6.19和代码6.20所示。

img

IntelCPU类和NVIDIACard类分别给出了CPU接口和GraphicsCard接口的实现。

接下来该轮到主板登场了,主板上应该有CPU和显卡的插槽,从软件的角度来说,就是主板类应该持有CUP和显卡接口的引用,如代码6.21所示。

img
img

在Mainboard类中,包含了两个私有的实例变量:cpu和gCard,它们的类型分别是CPU和GraphicsCard接口类型。我们知道,接口是不能直接实例化对象的,真实的CPU和GraphicsCard对象是通过setCpu和setGraphicsCard方法传递进来的,至于真实的CPU和显卡对象是什么,我们需要知道吗?不需要,我们只需要知道传进来的对象已经实现了对应的接口就可以了。这就像我们买主板时,不需要关心主板上的相应插槽最终插的是哪个厂商的配件一样,因为我们知道所选购的配件都是符合插槽规范的,可以和主板一起工作。

在Mainboard类的run方法中,调用CPU接口和GraphicsCard接口的方法,完成计算机的启动与显示工作。

Mainborad类只是与CPU和GraphicsCard接口打交道,并没有依赖于具体的实现类,所以在组装计算机时,可以任意创建实现了CPU和GraphicsCard接口的类的对象,然后“安装”到主板上。

最后,我们要组装计算机了,也就是编写一个Computer类,如代码6.22所示。

img

从这个例子中可以看到,Mainboard类并不关心“插”在它上面的CPU和显卡的具体类型,它只需要按照CPU和GraphicsCard接口中声明的方法使用显卡和CPU即可。而Computer类的main方法创建了CPU和GraphicsCard的实现对象,并通过setXxx方法把CPU和显卡“插”到主板上,接着调用Mainboard类的run方法启动计算机运行。

对于上面的例子来说,Mainboard类可以由一个人来开发,IntelCPU类可以由一个人来开发,NVIDIACard类可以由一个人来开发,而这三个人只需要按照CPU和GraphicsCard接口中声明的方法来进行编码就可以了,最终的程序通过Computer类来组装。