九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

多重继承不好的观点是错误的 -- 小评<松本行弘的程序世界>

首先得说, 一般某种语言的发明人写的关于自己语言的东西都是非常值得阅读的, 从别的牛人那里你也许能学会很多奇技淫巧, 但从语言发明人那里你能学到语言发明人本身设计的初衷, 以及设计时的一些抉择. 这种思路是独一无二, 绝无仅有的. 所以我在学习一个新语言时, 假如语言发明人有写书, 一定优先阅读.

目录:

语言发明人写的书又分两种, 一种是语言的教材, 这个几乎是惯例, 因为一个语言在初期, 没有其他人会用的时候, 语言发明人不教会别人怎么使用, 那可能就没有人会用了, 而他们这本教材的好坏甚至能决定他们语言最后能有多流行. 我听说一个有意思的言论, 说C语言之所以这样流行, 很重要的原因就是C程序设计语言这本教材实在写的太好了. 虽然有些夸张, 但是不无道理, 这个和现在一些开源库对文档要求很高的道理是一样的, 一个开源库的文档写的好不好, 对这个库最后是不是会被很多人使用有极大的关系. 当然, 也有反例, 鬼知道Anders Hejlsberg是怎么把一本C#的语言的教材C#程序设计语言, 写的就像是语言的规格说明书一样.
另外一种, 那就是除了教材外, 写关于自己语言的设计想法的书, 这种书就更加少了, 就目前而言, 我只看到过Bjarne Stroustrup写的C++语言的设计与演化和这本松本行弘的程序世界. 欢迎大家给我推荐其他的类似书籍. C++语言的设计与演化讲述了BS在设计C++时的各种抉择, 看完该书后, 我第一次明白C++到底是怎么设计成那么难用的, 当然, 同时也明白了怎么使用C++是正确的. 后来还反复的读了好几遍, 工作后, 也极力的推荐给使用C++的同事阅读, 事实上, 现在我买的这本书就还在我原来的同事那里, 虽然我已经离开原公司了. 如果说, 那个时候读C++语言的设计与演化还太无知(我那时候才刚刚参加工作), 并且C++又是我接触编程的第一门语言, 导致碰到一些可能习以为常的东西还如获至珍, 所以才这么喜欢的话, 那松本行弘的程序世界在我工作五年, 已经或多或少学习了不下于10门编程语言后, 仍然能给我很多启发, 就更加值得向大家推荐了.

Ruby的继承体系

多重继承不好的观点是错误的 – 松本行弘

这是我感觉是全书最精彩的部分, 本文也主要关注这一部分. 特别是对于多重继承部分的描述, 充分体现了一个优秀的程序员, 语言设计者的功底.

讲到这里, 我想先讲讲我对这部分内容的认识历史.
C++语言的设计与演化中, 我们知道, 多重继承在加入C++之前就有争议, 但是BS坚持自己的意见, 最终C++还是支持了多重继承. 并且为被很多人批评的多重继承进行了辩护, 他的确承认了多重继承可能带来的混乱, 但是认为, 在有的时候, 多重继承的抽象的确是有意义的, 并且这种抽象在有的时候会更加优美. 本着C/C++的设计原则, “程序员总是对的”, 不应该人为的去限制程序员, BS坚持的提供了多重继承这一工具, 并让程序员去决定, 什么时候用会导致混乱, 什么时候用可以让设计更加优美.
在成为标准以后, 多重继承的争议一直没有消失, 各种支持多种继承的语言甚至在怎么解决菱形继承上出现了不同的解决方案. 比如C++中, 菱形继承还引入了一个更扭曲的虚继承, Python支持多种继承, 并且因为所有的对象都继承于共同的基类Object, 导致任何多重继承其实都是菱形继承, 当你真的开始自己用多重继承时, 其实继承结构已经是一张网了, 对此, Guido Van Rossum的答案是用深度优先搜索, 靠基类排列的顺序来决定, 个人觉得不仅没有解决这个问题, 反而显的更加神奇.

而无数的JAVA用户者则是把C++的多重继承当作笑话和C++混乱的根源, 自豪的宣布JAVA没有这个问题. James Gosling给出了他自己的解决方案, 那就是没有没有C++自由的多重继承, 只支持对实现的单继承, 并引入了接口, 只允许接口的多重继承. 并且, 在继承接口和继承基类形式上特别加了一些区别, 所以实际中, 很多人并不把这叫做多重继承, 并宣称JAVA没有多重继承.
有意思的是, 高老头后来去了Google, 而我使用C++时尽量遵循的规范Google C++ Style Guide中就明确的规定了禁止C++的多种继承(不代表这两个事情有任何关系), 并且在Style Guide中自己给出了他们的Interface定义, 只允许使用此Interface进行类似JAVA的接口多重继承. 这真是趣事, 在一个Style Guide中, 去强行修改一个语言特性, 也足以证明C++的多重继承有多么臭名昭著了.

就个人感受, 以前开发的一个iOS游戏项目, 仅仅开发了半年, 就因为代码混乱不堪, bug多到无法维护, 项目一度在另外一个工作室被cancel, 然后我们中途再接手开发, 并且把多重继承看作是混乱的根源, 后来整理代码的很大部分工作就是用组件的方式去替代掉混乱的继承, 并且改善后设计清晰了很多, 再后来, 我开发一个Android项目的时候, 第一次真的在项目中去使用JAVA, 深刻的体会到了JAVA对继承的限制, 也真的去实践了GOF在设计模式中早就提到的”组合优于继承”这一设计原则, 也去体会了JAVA社区提倡的面向接口编程. 事实上, 我也逐渐的感觉到了, 其实, “继承本身就是一种强耦合”, 就是一种子类对父类的依赖耦合, 在一些书籍中提到, 甚至继承本身都是不提倡的, 也就是说, 提倡的是基于对象的设计(OB), 而不是面向对象的设计.(OO)

故事到这里就是我的认识了, 鉴于我的认识都来自于各种上述提到的流行语言和经典书籍, 我想大部分人可能会有和我类似的观点吧. 但是Mats不这么看.

Mix-in

本来只是为了跨越继承层次来共享代码, 现在却需要另外生成一个独立对象, 并且每次方法调用都要转送给那个对象, 这实在是不太合理, 而且执行的效率也不高.

Mats直说JAVA对多重继承的解决方式不够方便, 也不太合理. 他提供的解决方案是, Mix-in.
Mix-in按照以下规则来限制多重继承.

  • 通常的继承用单一继承
  • 第二个以及两个以上的父类必须是Mix-in的抽象类

Mix-in类是具有以下特征的抽象类.

  • 不能单独生成实例
  • 不能继承普通类

通过这种在接口和普通多重继承之间的折衷, Mix-in提供了对实现的多重继承, 同时对其进行了限制, 使得继承结构不会成菱形, 而是和单一继承一样的树型. 在Ruby中, Mix-in是通过模块(module)的概念来实现的. 作为例子, Matx用了Ruby中的Stream实现的例子.

Ruby的Stream结构

Ruby

C++的Stream结构

Cpp

从这点看, 为什么我说这是一种折衷方案呢, 因为相对于只允许接口的多重继承来说, 实现更加方便了, 但是, 对于真正的面向对象来说, 毕竟还是需要把一些实现给拆分成更细粒度的类, 才能符合Mix-in的要求. 比如看上面两个图就能发现, 同样的功能, C++需要的类要比Ruby需要的少. 从这点来说, Mats批评JAVA的组合方式为了共享代码需要生成一个独立对象是不方便的, 而实际上Max-in也会相对真正的多重继承来说需要更多的独立对象, 只是使用的方式不是组合, 而是继承. 当然, 这种限制能带来类似JAVA的更加良好的设计, 避免菱形继承及类似更加复杂的继承体系, 同时, 又比JAVA那种方式更加方便, 大家都知道, 加一个继承只要一个单词, 而加一个对象的组合调用, 往往需要增加N个函数接口, 以及N个调用.(虽然有种东西叫做委托) 有趣的事情是, 在编程这件事情上, 很多时候, 折衷的方法却往往是优秀的方法…

本书不足

  1. 作者在前言里面介绍, 是在杂志上连载的文章的基础上编辑修改来的, 所以有些地方其实能发现明显的重复, 甚至不仅仅是文字内容重复, 连Enumerable的那张表都原封不动的重复了两遍.
  2. 后面的章节趣味性有余, 但是感觉思想性稍微有些弱. 所以我看的时候过的比较快, 不过这也见仁见智吧, 也不排除我对相关内容兴趣不足的因素.

额外的声明

我在最新的文章中把所有关于书的链接从豆瓣改为亚马逊了, 假如你点击我给的链接过去并且买了书, 亚马逊会给我一些佣金, 当然, 我知道这没有多少, 但是聊胜于无吧, 以后的文章类似, 不再声明了. 我甚至都不知道这样无害的事情为什么需要声明, 但是好像在中国你需要这样做, 因为我看别人都这么做了.

分类:  编程 
标签:  Ruby  多重继承  松本行弘的程序世界  Multiple Inheritance 

Posted By 九天雁翎 at 九天雁翎的博客 on 2013年01月07日

前一篇: 写递归函数的正确思维方法 后一篇: C#特性杂谈