如何解决Ruby模块/混合方法冲突

时间:2019-01-18 07:39:59

标签: ruby inheritance methods

以下,A继承了F,后者继承了E,因此在initialize实例上调用A会调用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

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

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

有没有办法引用这些方法?

2 个答案:

答案 0 :(得分:4)

您遇到的问题是Ruby中没有多重继承。因此将模块作为祖先插入,而F#initialize遮盖了E#initialize。如您所见,使用F#initialize可以轻松访问super(f);但是另一个需要黑客才能访问:我们可以直接从模块中选择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是继承)。

答案 1 :(得分:3)

您可以为此使用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] 

另请参见Module#ancestorsArray#indexInteger#timesEnumerable#reduce(又名inject),Object#methodMethod#call。 / p>

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