最后讨论一下无法继承多种类的装配问题该问题的原因在于Java不允许多层继承
Java不能进行多层继承这一点大多经常作为其优点而不是缺点而被提出来C++允许多层继承但结果却致使程序变得非常复杂而且还产生了难以理解的错误
多层继承在装配时会很复杂
多层继承会在如下几个方面导致混乱第一是名称的沖突在继承具有相同名称的其他类时不知道是哪个类的方法将会被调用(图)这一点是在一个类继承两个已经继承了某一个类的类时经常发生的问题这种继承称为菱形继承(图)在分别改写(override)最上层的类的方法后就会引起被最下层的类所调用的问题
图●在需要继承两个具有相同名称的方法的超级类时其名称就会发生沖突这一点是经常被作为多层继承的问题而指出来的地方
图●菱形继承的一个例子在同一个人具有社会人和学生两种性质的时候就必然会引起此类问题
第二类的层次也会变得很复杂如果只有单继承类的结构仅仅只会分支成树状而如果是多层继承一下子就会变得很复杂如果多次反复地进行多层继承掌握类的层次关系几乎就将变得不可能
由于Java只允许单继承因此就不会引起这种问题不过用户当然也希望通过将多个不同的类组合起来生成新的类为了解决这一问题Java就提供了被称为接口的多层继承的功能
日本产业技术综合研究所信息处理研究部的主任研究员一杉裕志说Java只允许单继承从编程语言的装配的角度来讲是一种很不错的作法甚至有人表示Java利用接口来取代多层继承给人的感觉非常不错此前Java之所以如此普及就是因为Java的语言标准采取了适当的折衷(东京大学研究生院处理理工学系研究专业专门从事计算机科学的萩谷昌己教授)
接口不能继承装配
图●Java只允许继承一个装配因此就必须完整地复制代码
不过基于接口的伪多层继承存在致命的缺点这就是指接口无法继承装配Ruby语言的开发者日本的松本幸弘指出使用接口取代多层继承是Java的高明之处不过放弃装配的继承则令人非常痛心
继承个类的功能时Java将会复制某一个类的源代码结果相同的代码就会分散在各种不同的位置可维护性就会显着降低(图)将相同的处理过程集中到名为类的单位中来提高面向对象的优点没有得到充分的利用从市场销售上来讲Java是一种取得了巨大成功的语言但是从编程语言的装配角度来看我认为它是一个失败的作品(松本)
Ruby语言利用Mixin实现装配的继承
实际上松本开发的Ruby语言也和Java一样只允许单继承不过Ruby则利用相当于Java接口的方法实现了装配的继承这就是被称为Mixin的方法
Mixin是指仅将程序中具有再利用性的功能部分集中到被称为模块的单位中以供其他的类来使用模块与类一样其本身无法利用new运算符来生成只有include即嵌入到其他的类中才能使用(图)
LIST 就是使用了Mixin方法的简单Ruby代码定义了一个可以输出test字符串的名为TestModule的模块TestClass类嵌入了该模块这样一来TestClass类就可以像调用自身的方法一样调用TestModule模块中的方法
不过Ruby的Mixin所解决的只是由多层继承所造成的继承关系的复杂性在多个模块存在同名的方法时就会产生与多层继承相同的问题Ruby优先处理由后面嵌入的方法(注)
图●Ruby中的Mixin功能将集中了各种功能的模块嵌入到类中来使用
LIST ●使用Ruby中Mixin功能的代码TestClass类可以像使用自己的方法一样使用TestModule模块所装配的方法
利用与类不同的单位进行程序再利用的MixJuice语言
目前一种利用与Ruby不同的方法来提高程序的再利用性的名为MixJuice的语言也在开发之中这是一种基于Java的独立语言
LIST ●MixJuice的代码程序以module(模块)为单位进行描述而类则由module来分割
为了程序的再利用即便MixJuice也使用了与类不同的编程单位这种单位与Ruby一样被称为模块MixJuice语言的开发者日本产业技术综合研究所的一杉表示在基于Java等语言中常见的类的程序设计中通过多个类的协作实现某一种功能时类就无法进行再利用因此应该有一个与类不同的可以再利用的编程单位
MixJuice语言中当然也有类但是编程时所处理的并不是一个类的源代码而是以包含了各种可协调工作的类的模块为单位来描述程序重新利用模块进行扩展时描述的是模块之间的区别
LIST 就是实际的源代码程序是以模块为单位来组织的类则在模块中使用define关键词进行定义如本例所示通过追加模块来描述与现有类的区别把使用多个类而实现的功能集中到了一起
必然引起装配缺陷
不过这种方法必然会产生其他问题就是说要使用MixJuice中的模块来扩展类时就会产生装配缺陷
图就是一个具体的例子模块m和m均继承了模块mm定义了在m中定义的类S的派生类B而m则向S类追加了方法如果是普通的方法就不会产生问题但是这里却定义了一个利用派生类来强制装配的抽象方法实际上它的派生类A添写了装配
如要同时使用这两个模块连接时就会产生错误因为类B没有装配由m追加到类S中的方法m()为了解决这个问题无论是谁拥有多么丰富的知识都必须对方法进行装配这种问题是因模块具有方法追加和子类(派生类)追加等种扩展的方向性而引起的(图)
图●装配缺陷的例子由m追加到类S中的抽象方法的存在与m没有任何关系如果同时使用这个模块时就会产生编译错误
图●存在二个以上的扩展方向性时就会产生装配缺陷拿MixJuice来说就是子类和方法的扩展性
提出了新的分割单位的面向侧面编程(AspectOriented programming)
像MixJuice语言那样利用横跨多个对象的单位来把握系统的观点称为关注分隔(Separation of ConcernsSoC)类的相互作用也属于关注的一种这种观点并不是仅仅单纯以对象为单位还要由其他侧面来分割系统
最近这种观点已经开始受到越来越广泛的关注基于该观点的代表性编程范式(paradigm)就是面向侧面编程它就是以关注为分隔单位的如果存在多个与类相同的关注就通过将这些关注组合起来实现一种功能这种功能就是侧面(Aspect)
图就是具体的例子把移动图形来刷新画面的处理过程定义为一个侧面图形是由点和线组成的每一个点和线中均存在移动的方法和用于设置位置的方法这些动作相互协作就可实现称为画面刷新的侧面
LIST 就是使用基于Java的面向侧面语言AspectJ来描述侧面的源代码在一个被称为移动图形的程序段中定义了多个类的多个方法图形的移动结束后最后执行重新刷新画面的处理过程
图●利用关注分割类的图示点和线以及他们各自所具有的定位和移动的方法被划分成了相同的关注该图摘自面向侧面编程的倡导者Gregor Kiczales在面向侧面编程技术研讨会上发表的演讲资料
LIST ●利用AspectJ描述侧面的源代码将用于实现图像移动的各种类中的方法归纳为movemove结束以后执行由after描述的画面刷新摘自Gregor Kiczales在面向侧面编程技术研讨会上发表的演讲资料
与面向对象并不矛盾
面向侧面编程在对象以外导入了分割系统的单位提高了程序的再利用性和扩展性如果只有对象单位有分割的轴也许确实就会存在一定的限制吧
不过面向对象本身就是公认难度很高的编程范式尽管如此面向对象语言能够普及到如此程序恐怕是因为只有对象单位有分割的轴吧正因为轴只有一个好歹才能够理解如果还要加上被称为侧面的轴也许有很多程序员就会产生混乱
另外面向侧面则是自由度很高的编程范式正是因为自由度高程序员才会感到苦恼因为他们不知道应该如何分割或组织程序也就是说在某种程度上缩小自由度并给程序员提供编程指南也可以说是编程语言的任务
面向侧面是一种扩展了面向对象的编程范式如果认为面向对象的自由度合适就可以继续使用它就连面向侧面的倡导者加拿大英属哥伦比亚大学的Gregor Kiczales本人也表示没有必要非要去勉强地使用侧面来编程程序员要根据自己的开发风格在追求一种易用性和扩展性适当平衡的同时使用这种编程范式