【问题标题】:How is Ruby module inclusion not really 'multiple inheritance' and how does the Ruby style avoid the problems associated with multiple inheritance?Ruby 模块包含如何不是真正的“多重继承”,Ruby 风格如何避免与多重继承相关的问题?
【发布时间】:2011-05-01 07:51:13
【问题描述】:

据说,Matz 说过“mixin 几乎可以完成多重继承所做的所有事情,而没有相关的缺点”(Matz 的话)。”

首先,为什么 Ruby 模块包含不是“多重继承”?在我看来,模块和类之间几乎没有区别。当模块用作超类时,您无法实例化模块这一事实无关紧要。

我还知道,连续的模块包含形成了一个从类向上延伸的单一继承链(不是树)。但是,对我来说,这不足以将它与“多重继承”区分开来,因为 Python 多重继承系统也“线性化”了超类链(使用 C3 algorithm),只是 Ruby 的“线性化”过程要简单得多。

那么,在像 Python 这样的语言中,Ruby 模块 mixins 与多重继承的确切区别是什么?为什么 Python 采用 c3 MRO 算法背后的论点不适用于 Ruby?如果它们确实适用 - 为什么 Ruby 决定不采用这种算法?

谢谢

【问题讨论】:

标签: ruby multiple-inheritance mixins


【解决方案1】:

使用 MI,可以将出现的许多问题简化为实现细节;您不能笼统地谈论“多重继承”,而不谈论细节。因此,当您说“多重继承”时,我会假设您的意思是“C++ 多重继承”。

多重继承最常见的问题是Diamond Problem。如果同一级别的多个超类定义了同一个方法,如何知道子类调用了哪个方法?

使用模块,这个问题很容易解决 - 最后包含的模块总是“获胜”。你不能像 C++ 中的类那样“同时”包含多个模块。所以这个问题永远不会出现。

当模块用作超类时,您无法实例化模块这一事实无关紧要

我不同意。

首先,模块在 ruby​​ 中永远不会“用作超类”;只有超类是。

其次,通过多重继承,确切地知道构造函数(和析构函数!)被调用的顺序是not a trivial matter。 ruby 模块不允许实例化的事实完全抑制了这个问题。

【讨论】:

  • 谢谢,但是更好的比较是 Python 的 MI,而不是 C++ 的。我再次恭敬地不同意包含的模块和“超类”:)。包含模块的操作几乎与 Ruby 中的“超类”完全相同——即类的超类指针指向包含模块(就像它指向超类一样)。此外,Ruby 的模块遵循“最后包含的模块总是“获胜””的规则这一事实可以被视为简单的线性化算法——类似于 Python 的 c3 线性化——所以我看不出这本身如何将它与 MI 区分开来。
  • “几乎”与“完全”不同。差异很重要。例如,“super”不适用于模块。但无论如何我都没有意识到这是一个 Python 问题。我想我应该得到 -1 :/
  • super 适用于 in 模块,但不适用于 for 模块,因为模块不能被子类化。模块 M 可以定义调用 super 的方法,但只有在包含 M 的类定义了 super 时它才会起作用。否则它将是未定义的。
  • 如果我有两个模块(A 和 B),并且 B 包含 A,然后我将 B 包含到 C 类中,构建一个实例并执行一个方法。如果该方法在 C、B、A 上定义为“super”,它将调用每个方法并沿链向上。在这个例子中,super 在模块中工作得很好——你特别说“超级在模块中不工作”。 Super 在模块中运行良好。我不知道你所说的“模块不能被子类化”是什么意思——在这个例子中,B 有效地继承了 A,而 C 子类化了 B。super 按照你对子类化的期望工作——它从 C 上升到B 到 A。
【解决方案2】:

代表 Mladen 将其添加为实际答案,因为我发现它非常有帮助,而且我猜测无论 SO 用它做什么疯狂的事情,答案都会得到更好的索引。

这是一篇关于这个问题的好文章,看看它是否能回答你的问题 问题:http://artima.com/weblogs/viewpost.jsp?thread=246488 – 姆拉登 2010 年 10 月 28 日 18:23

【讨论】:

    【解决方案3】:

    查看 Pragmatic Press 的“Metaprogramming Ruby”一书。它以非常易于阅读和理解的方式包含对此的非常详细的解释。 http://pragprog.com/titles/ppmetr/metaprogramming-ruby

    我无法回答您的大部分问题,因为我不懂 python。但是为什么它不是多重继承的基础是当模块被包含或类被模块扩展时,ruby 将模块注入到继承链中。

    class Foo
      include Bar
    end
    
    module Bar
    end
    
    foo = Foo.new
    

    这会产生一个继承链,其中 foo 是 Foo 的一个实例,而 Foo 继承自 Bar。

    它比这更棘手,并且继承注入发生的顺序有一些规则。我参考的那本书解释得很好。

    【讨论】:

    • 谢谢,呵呵,但是你觉得你能把书里的内容概括得更详细一点吗?注意:我不需要知道如何注入模块的细节,更多的是他们为什么选择这种方法而不是典型的多重继承方法的理论,以及它与普通 MI 方法的不同之处。 :) 不好意思问,但我不会买这本书(我已经有太多要读的书了,呵呵)。
    • 我读过“Metaprogramming Ruby”,但我不知道栏杆问题的答案。
    【解决方案4】:

    在 ruby​​ 中最重要的是您使用每个包含的模块覆盖以前的数据/函数定义。

    所以这很糟糕,因为理论上每个新模块都应该注意提供对被覆盖的先前代码的访问权限。

    所以编写新模块的示例代码是(逻辑上):

    old_function = fun
    fun = define_new_function
      do_you_staff
      call old_function
    

    你不必使用旧函数,虽然在大多数情况下它很有用,但有时重写整个代码更容易。

    每个包含的模块都会构建覆盖的方法链,因此多重继承不会有问题,但包含模块的顺序变得更加重要。

    这种方法也称为猴子补丁——在 Ruby on Rails 中广泛使用的术语。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-13
      • 2020-02-04
      • 1970-01-01
      • 2012-05-02
      • 1970-01-01
      • 1970-01-01
      • 2013-02-20
      • 1970-01-01
      相关资源
      最近更新 更多