4

我想在不破坏查找链的情况下合并两个 Ruby 模块。基本上我希望的行为BothAnB就像我连接来自 A 和 B 的文本源代码并且新的 foo 替换旧的一样。当 MRO 线性化继承菱形时会出现问题。

module O
  def foo; puts "O" end
end

module A
  include O
  def foo; puts "A"; super end
  def aaa; puts "aaa" end
end

module B
  include O
  def foo; puts "B"; super end
  def bbb; puts "bbb" end
end

module BothAnB
  #insert magic here such that a class C that includes BothAnB:
  # C.new.foo => B O
  # C.new.aaa => aaa
  # C.new.bbb => bbb
end

module JustA
  #insert magic here such that a class C that includes JustA:
  # C.new.foo => A O
  # C.new.aaa => aaa
  # C.new.bbb => FAIL
end
#and similarly JustB

A 和 B 是相当复杂的模块,可以具有深度继承链(这是针对允许程序员这样做的元编程框架)。

Include B, A不起作用,因为不是查找 BothAnB->B->A->O,而是我需要它是 BothAnB->B->O(和可选的 ->A)。我已经接近了:

  • 深度克隆 A 的整个继承树(去除菱形)
  • undef_method在 A 的克隆上删除在 B 中找到的方法
  • 制作一种新方法Module来反映这种行为

还有比这更好的解决方案吗?理想情况下,我希望在调用BothAnB.ancestors.

[注意:在根据 Phrogz 的反馈得到两个答案后,我完全改变了问题,所以如果它们看起来无关紧要,那就是]

4

4 回答 4

2

这会为你解决吗?

module M1
  def foo; 42; end
  def bar; 17; end
end

class Base
  def foo; 0; end
end

require 'remix' # gem install remix 
class X < Base
  include_after Base, M1
end

p X.new.foo, #=> 0
  X.new.bar  #=> 17
于 2012-07-31T21:05:05.300 回答
2

这是另一个可能的技巧:

module BothAnB
  include A
  include O.clone
  include B
end

class C
  include BothAnB
end

C.new.foo
C.new.aaa
C.new.bbb

# output:
B
O
aaa
bbb

在这里,我们使用super指向B#fooO#foo不是A#foo

如果O很复杂并且包含其他东西,它可能需要更多这样的魔法:

module O
  # don't do this:
  # include Whatever

  # do this instead:
  def self.included(base)
    base.send(:include, Whatever.clone)
  end
end
于 2012-08-20T11:41:10.360 回答
1
M1parent.send(:remove_method, :foo)

您必须将其从那里远程,M1parent因为这是定义它的地方,M1.send(:remove_method, :foo)例如,因为方法 foo 是在 M1parent 上定义的,所以它不起作用。

于 2012-07-31T15:06:00.327 回答
0

我的建议如下:登录http://bugs.ruby-lang.org/并提交功能请求,或者,如果您更有信心,可以提交错误请求。因为这基本上是Ruby的问题。当前的 Ruby 行为在没有先前经验的情况下是出乎意料的,因此应该更改为预期的行为:这样您就可以通过简单地调用来获得所需的内容

module BothAnB
  include A
  include B
end

正如您自己指出的那样,解决方法肯定是可能的。但是这个恕我直言的Ruby行为是不正确的。

所以在那之前,你必须避免打电话给 super ,我建议你改用

O.instance_method( :foo )

并从 B 和 A 模块而不是便利关键字 super 调用它:

module A
  include O
  def foo
    puts "A"
    O.instance_method( :foo ).bind( self ).call
  end
end

# do same for B and it'll work
于 2012-08-14T10:31:19.407 回答