如何解决 ruby 模块/mixin 方法冲突



在下文中,A继承了F,继承了E,因此在A实例上调用initialize会调用A#initialize,其优先级高于E#initializeF#initialize

module E
  def initialize(e)
    @e = e
  end
  def e
    @e
  end
end
module F
  def initialize(f)
    @f = f
  end
  def f
    @f
  end
end
class A
  include E
  include F
  def initialize(e, f)
    # ...
  end
end

我需要从 A#initialize 的方法主体中引用 E#initializeF#initialize,分别传递 ef 作为参数,以便我得到这个结果:

a = A.new("foo", "bar")
a.e # => "foo"
a.f # => "bar"

有没有办法参考这些方法?

你的问题是 Ruby 中没有多重继承; 因此,模块作为祖先一个接一个地插入,因此F#initialize掩盖了E#initialize。如您所发现的,使用 super(f) 很容易访问F#initialize;但是另一个需要黑客才能访问:我们可以直接从模块中选择initialize方法,因为它是祖先;然后将其绑定到当前对象并运行它。

def initialize(e, f)
  E.instance_method(:initialize).bind(self).call(e)
  F.instance_method(:initialize).bind(self).call(f) # equivalent to super(f)
end

但是,我不建议这样做。如果你需要运行多个初始化器,你最好使用组合,而不是继承(需要明确的是,include继承)。

你可以使用 Method#super_method 来实现此目的。

module E
  def initialize(e)
    @e = e
  end
  def e
    @e
  end
end
module F
  def initialize(f)
    @f = f
  end
  def f
    @f
  end
end

class A
  include E
  include F
  def initialize(e, f)
    select_initialize(E).call e
    select_initialize(F).call f
  end
  private
  def select_initialize(mod) 
    self.class.
         ancestors.
         index(mod).
         times.
         reduce(method(:initialize)) { |m,_| m.super_method }
  end
end
puts A.new("E", "F").f
  #=> F
puts A.new("E", "F").e
  #=> E

注意:

A.ancestors
  #=> [A, F, E, Object, Kernel, BasicObject] 

另请参阅模块#祖先,数组#索引,整数#时间,枚举#reduce(又名inject),对象#方法和方法#调用。

当然,EF可能包含类实例所需的其他实例方法。

最新更新