你认为Ruby的Mix-in和Java的interface哪一种实现多重继承的方式比较好?

2011-11-08 17:19:55 +08:00
 reducm
最近在学习Ruby,由于此前一直使用Java,他的面向接口编程的理念对我影响颇深,所以对Ruby的Mix-in的多继承方式有种种的疑问。现假设有这一场景:一父类(或抽象类或接口)< 内容>,两个子类 <文章>和<评论>,他们都继承了<内容>的<读>方法,Java中我们可以定义一个接口<内容>,然后两个子类分别实现这个接口,再重写<读>方法(根据读取的内容而不同的操作),在编写调用类中,直接针对<内容>这个接口编写就行,在程序运行时根据实际给我们的<文章>或是<评论>的子类而动态调用各自的<读>方法,从而实现多态性。而Ruby的Mix-in则不然,Mix-in是把具体实现功能直接写在Module中,然后哪个类需要这种具体功能就把Module include进来。在我看来Ruby的类和Module是的的确确的"继承"(强耦合关系),这对于减少耦合程度提高扩展性的设计方式来说有诸多不利。Java中interface是一个标准,我们很多时候都是针对标准去开发,减轻了程序的复杂性。在Ruby里面有这种根据标准的方式去编程吗?假如没有标准的话,像上述的编程场景,Ruby可以在<文章>类的读方法是readArticle,在<评论>类方法里是readComment,这样的代码也不好维护,而且就Module的Mix-in方式来说,如有某些功能例如在插入后要调用类的其他方法,会在Module里写self.otherMethod,这不就是在设计时候就需要了解类的内部吗?之前也写过PHP(伪面向对象),感觉本身对动态语言的设计方式有许多的不解,请各位大大调教
6438 次点击
所在节点    问与答
10 条回复
lldong
2011-11-08 17:53:12 +08:00
记得关于这点在《松本行弘的程序世界》中有专门的讨论
chloerei
2011-11-08 18:41:26 +08:00
《松本行弘的程序世界》里面说了,Mix-in 就是多继承的一种形式。
chloerei
2011-11-08 18:49:25 +08:00
两种形式各有各的好,对我来说,Mix-in 用起来很好很神奇,代码量很少,随之程序的复杂性降低。“动态”带来的种种不确定性,用 unit test 来约束。
ayanamist
2011-11-08 20:14:59 +08:00
Scala用的是Mixin,Python用的是Mixin。
duhastmich
2011-11-08 20:38:46 +08:00
@ayanamist python 是多继承吧,php5.4也有mixin了
duhastmich
2011-11-08 20:40:29 +08:00
11.11有人去ruby技术大会吗?
reducm
2011-11-08 20:55:28 +08:00
@chloerei 能举举例子如何用单元测试约束吗?
keakon
2011-11-08 21:03:27 +08:00
2种方式要达到的目的都不一样。

Interface只是提供了一个方法的签名,而没有提供实现。任何实现这个接口的类都需要自己去编写实现代码。
Mix-in可以真正地继承这些方法的实现,你直接拿过来用就行了。

并不是任何引入耦合的地方都会影响程序的复杂性。继承是最强的耦合方式,但合理使用继承,只会降低复杂性。

动态语言的一个好处就是减弱了这种耦合,你可以在运行时随意替换对象的类,一个类的父类,或者类和对象的方法,它们并不像静态语言那样死死绑定在一起的,更像是组合的方式。
而反观Java的接口,它同样也是耦合,只是没有实现而已,你没办法撤销一个类的接口。
Objective-C也多少有些动态性,虽然也是使用接口,但类的方法可以在运行时随意更改。

动态语言(准确来说是动态类型语言)的另一个好处就是duck typing可以减少很多代码量。
在参数传递时,不需要知道它是什么类型,只要它能做这件事就行了(有这个方法或属性)。不能做就抛出异常,反正是调用它的人传错的参数,与方法本身无关。
Java我就不说了,既然你是Java程序员,你也应该知道设计接口和规范要花多大精力,使用时还得避免各种代码的smell。
但合格的程序员是知道怎么去避免传递不符合要求的参数的,这些Java中看上去很华丽的设计,大部分情况下只不过是在限制程序员的自由和浪费时间而已。之所以Java程序员那么注重设计模式,而动态语言中却没那么注重,根本原因就是大部分设计模式就是为了解决动态语言中不存在的问题。

而在不用担心参数的类型后,动态语言的函数/方法还获得了巨大的灵活性和通用性。
如果你看过jQuery(一个JavaScript库)的接口的话,你就会惊奇同一个方法为什么可以支持那么多种类型和数目的参数了。如果是用C++的话,不得不用一堆重载和模板来实现。
而在Python中,更是可以通过列表和字典参数来实现超级通用的方法(不管传什么类型的参数,顺序如何,数目如何,通通支持)。这对于Java来说完全无解。

对于你所说的读方法,如果这2个类要通用的话,在设计时就会人为地规定采用相同的方法名(如read),这和接口的约束其实是一样的,只是由人来对代码负责而已。要真设计得很差的话,你也可以用别名。

而如果要调用其他类的方法,我觉得你应该调用Mix-in自己的(空)方法(即方法存根),然后再让引用它的类去覆盖实现。

最后,JavaScript的原型继承虽然不支持多重继承,但方法和类可以随意组合和分离,因此大可使用apply、call来借用其他类的实现。而Python支持真正的多重继承,且也可以用metaclass来mixin。
不过就我个人而言,还是习惯mixin的方式,因为现实中基本上遇不到需要多个父类的意境。
reducm
2011-11-08 23:51:54 +08:00
@keakon 感谢大大的回答,帮我理清了许多东西
reus
2011-11-09 16:04:46 +08:00
php5.4引入的不是mixin,是trait
trait和mixin是不同的,这个论文说得很详细:http://scg.unibe.ch/archive/papers/Scha02bTraits.pdf

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/21017

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX