继承与接口
本文仅仅表达了我对面向对象程序设计方法中继承和接口用法的理解。
在文档 [1] 中,讨论了有关 GObject 子类继承的问题,并在后半部分指出了类的继承所存在的问题,并企图挖掘类的继承是多么的没用。这显然是徒劳的,因为类的继承的确是有用的,并且我们已经见 识过它不计其数的应用,例如几乎所有的 GUI 库都是基于类的继承的方式而设计的。
那么,为什么会这样?这是因为 GUI 库的设计者们发现类的继承非常适合解决他们的问题。
那么,他们的问题是什么问题?答案很简单,这肯定跟对象的属性和方法有关系。当然,这个答案连我都不满意,但它指明了分析的方向。我们沿着这个方向分析一下。
如果对象 A 所拥有的属性和方法,对象 B 也拥有,但是对象 B 还拥有对象 A 所没有的属性和方法,那么对象 B 便可以继承对象 A。
简而言之,只有当对象 A 的属性和方法是对象 B 的真子集时,即 [tex]A\subset B[/tex] 时,对象 B 继承对象 A 才有意义,因为这样可以节省重复编写相同的代码,即代码复用。
事实上,对象 A 与对象 B 之间的属性和方法的组合可以分为 9 种情况,如下图:
类 的继承仅能解决这 9 种情况中的一种,即属性与方法同时表现为包含(或被包含)的关系。比如 MP3 可以继承 U 盘,因为 U 盘所具备的属性和方法,MP3 也具备,但是 MP3 还多了音频播放的相关属性与方法。U 盘和移动硬盘之间便不可能存在继承/被继承的关系,因为它们的属性与方法顶多能形成一个交集。U 盘与键盘,虽然名字都有个“盘”,但它们的属性和方法连交集都谈不上。
基于上面的分析,我们从继承无法解决的那 8 种情况中选取一种来研究一下。比如,选取属性相离但方法相交的情况,该情况与那个 U 盘和移动硬盘的示例相对应。
U 盘和硬盘,属性是不同的。U 盘的属性是闪存结构,而硬盘的属性是磁碟+磁头+马达结构,但它们都有一块芯片,可以通过计算机主机 USB 接口与内存进行数据交换。这块芯片主要负责的工作就是数据的读入和写出,这就是 U 盘和移动硬盘所共有的方法。
假如我们非要用类的继承来模拟 U 盘与移动硬盘的关系,这样做的结果不仅不能解决代码复用问题,反而会增加冗余代码。因为必须要将 U 盘与移动硬盘所共有的属性与方法提取出来,形成一个基类,然后由这个基类派生出 U 盘和移动硬盘,如下图所示:
好像 C++ 语言里有一种“抽象类”的概念,专门用来制造上图中类似“盘”的东西。我认为,既然一个类无法形成实体,那么这个类就不应该存在。虽然抽象类可以干很多事情,但事实上它所干的那些事情并不是它的本意。
虽然,我们都是“人类”,但是“人类”这个抽象类对于我们人类本身而言一丁点意义都没有。只有在于“非人类”这种对象打交道的时候,“人类”这个抽象类才可能会有意义。
在设计抽象类的时候,我认为一定要考虑清楚,有没有和这个类并驾齐驱的其他类,并考察它们之间是否有相交的方法。例如,计算机的核心构件只有三类东西,一种是“盘”,一种是“线”,一种是“电”。这三类东西,虽然可以并驾齐驱,但是它们之间并没有相交的方法。
在 一些网络游戏里,抽象类貌似有点意义。例如,角色划分有人族、兽族、精灵族等等,其中每个族都可以对应一个抽象类,这些抽象类存在相交的方法,例如打 boss 升级,人族、兽族、精灵族中的对象,其升级的比例可能会不一样,比如兽族可能升的快,精灵族升的慢。毕竟扮演野兽,要比扮演神仙需要多付出一点心理承受 力。
在处理 U 盘和移动硬盘二者之间关系的问题上,也许 Java 给出了更好的思路。它说,用接口吧!
是阿,U 盘和移动硬盘,虽然本质(属性)上有所区别,但是它们的接口都是 USB。并且,只要它们插到计算机上,主机不关心那个东西是 U 盘还是移动硬盘,它只需要知道对方是个 USB 设备就 ok 了。这样,我们不需要在计算机主机中单独为 U 盘创造一种 U 盘接口,也不需要单独为移动硬盘创造一种移动硬盘接口。
虽然,USB 接口的存在,既没有让 U 盘的设计变的简单,也没让移动硬盘的设计变的简单,但是它让计算机主机的设计变的简单了,并且让 U 盘和移动硬盘的易用性也得到了提高,你只需要将它们插到一种接口中即可。
接 口,通常表现为“协议”。例如,tcp/ip 就是接口。我们只要上 internet,所访问的所有计算机基本上都是具有 tcp/ip 这种协议的计算机,我们不关心这种计算机上安装的是 Linux,还是 Linux,还是 Linux。虽然 tcp/ip 没有简化网卡的设计,但是它简化了计算机操作系统的设计,并且让网络的易用性得到了提高,大家只需要打开 firefox 就可以了。
总之,接口不是用来搞代码复用以简化对象设计的,而是用来简化对象的上层建筑的设计。
既然 tcp/ip 之类的软件已经采用了接口的设计方式,那么为什么要在面向对象程序设计里再去强调这个概念?
那是因为面向对象程序设计中的继承的概念一方面可用于接口的实现,另一方面可实现接口的继承。
使用接口,很简单,只需要将一个类“继承”这个接口类,然后实现接口所规定的方法(行为)。
接口的继承,这也很好理解,比如 USB 协议有 1.0 版、2.0 版,TCP/IP 协议从 v1 到现在的 v4 和 v6。接口版本的升级往往要向后兼容,所以面向对象程序设计中的类的继承的概念,很自然的可用于表示接口的升级。
如果明白了以上的概念,那么下一篇便可以尝试 GObject 的接口模拟。
~ End ~
参考文档
[1] GObject 的子类继承
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com
2015年3月20日 17:47
"好像 C++ 语言里有一种“抽象类”的概念,专门用来制造上图中类似“盘”的东西。我认为,既然一个类无法形成实体,那么这个类就不应该存在。虽然抽象类可以干很多事情,但事实上它所干的那些事情并不是它的本意"
我觉得博主对C++抽象类的理解可能有点偏差,C++所提供的抽象类,实际上就是开发给别人的接口。
客户开发人员只要按照接口来使用API就可以,不用关心实现API的人具体调用的那个实现体。
对抽象类的继承更应该理解为是对接口(或者方法的继承)。
不知道我解释的是否清晰:)