Ruby mixin - 多重继承

时间:2017-05-28 22:32:32

标签: ruby inheritance mixins

我最近学会了如何通过模块实现继承。但是,如果我想实现多重继承,我看不到干净的解决方案。 这是使用单继承并调用超类初始化方法的原始示例。:

module ModuleB  
  def initialize
    puts "initialize from ModuleB"
    @b = 5
  end
end

module ModuleA
  include ModuleB

  def initialize
    super
    puts "initialize from ModuleA"
    @a = @b
  end

  def action_1
    @a = @b + 1
  end
end

class ClassA
  include ModuleA
  def initialize
    super
    puts 'initialize - method in ClassA'
    @c = @a
    @d = @b
    puts "a = #@a"
    puts "b = #@b"
    puts "c = #@c"
    puts "d = #@d"
  end

end

instA = ClassA.new
puts instA.action_1

但是,如果ClassA中包含额外的ModuleC,我们如何调用“超类”来初始化mehods?

    module ModuleB  
      def initialize
        puts "initialize from ModuleB"
        @b = 5
      end
    end

    module ModuleA
      include ModuleB

      def initialize
        super
        puts "initialize from ModuleA"
        @a = @b
      end

      def action_1
        @a = @b+1
        @a = @b + 1
      end
    end

    module ModuleC
      include ModuleB

      def initialize
        super
        puts "initialize from ModuleC"
        @a = 'cute Zuzia'
      end

      def action_1
        @a = 'something'
      end
    end


    class ClassA
      include ModuleA
      include ModuleC
      def initialize
        super  # which initialize will be called? From moduleA or from moduleC?
                # what if I wanted to invoke ModuleC initialize from classA instance as well?
        puts 'initialize - method in ClassA'
        @c = @a
        @d = @b
        puts "a = #@a"
        puts "b = #@b"
        puts "c = #@c"
        puts "d = #@d"
      end

    end

在这种情况下如何使用mixin启用多重继承?

2 个答案:

答案 0 :(得分:3)

  

将调用哪个初始化?来自moduleA还是来自moduleC?

class C
  include M
end

非常简单:它使M的超类CC的超类M的超类成为M的超类。换句话说,它只不过是标准的无聊的旧类继承。 (更确切地说:它创建了一个类,它与C共享其方法表指针,常量表指针和类变量表指针,并使该类成为C的超类和前一个超类{ {1}}这个新创建的类的超类。它还检查M的祖先是否已经在C的祖先链中,在这种情况下它什么都不做。)

module M1
  include M2
end

显然,这不能使M2成为M1的超类,因为模块没有超类。它所做的只是记录M2M1的祖先这一事实,以便将来M1 include成为一个类和一个为M1创建了一个类,M2将包含在这个新创建的类中。

所以,它只是类继承。简单,无聊,类继承。

让我们看一下代码中的情况:

module ModuleB; end

好的,我们已经定义了一个名为ModuleB的模块。

module ModuleA
  include ModuleB
end

我们已经定义了一个名为ModuleA的模块,它在其中的某个位置存储了这样一个事实:当include进入某个类时,ModuleB应该是include也是。 (让我们假设有一个实例变量@__included_modules__或类似的东西,而Module#include只是实现为@__included_modules__ << m。)

module ModuleC
  include ModuleB
end

我们再次定义了一个名为ModuleC的模块,它在其中的某个位置存储了这样一个事实:当include进入类时,ModuleB应该是{{1}也是。

include

让我们一步一步。

  1. class ClassA include ModuleA include ModuleC end 的超类是ClassA(如果没有明确指定,则为隐式超类)。
  2. Object首先检查include ModuleA的祖先链中是否已ModuleA。它不是。
  3. 因此,它创建了一个新类(让我们称之为ClassA)。
  4. 它使〚ModuleA′〛Object的当前超类)成为ClassA
  5. 的超类
  6. 它使〚ModuleA′〛成为〚ModuleA′〛的超类。此时,祖先链如下所示:ClassAClassA〚ModuleA′〛ObjectKernel
  7. 现在它只是按照记录的顺序重复BasicObject中记录的每个模块的所有内容。
  8. 首先,Ruby会检查ModuleA的祖先是否已经在ModuleB的祖先链中。它不是。
  9. Ruby创建了一个类,我们称之为ClassA
  10. Ruby创建〚ModuleB′〛的当前超类,当然〚ModuleA′〛Object的超类。
  11. 它使〚ModuleB′〛成为〚ModuleB′〛的超类。此时,祖先链如下所示:〚ModuleA′〛ClassA〚ModuleA′〛〚ModuleB′〛ObjectKernel
  12. 现在我们已完成BasicObject行并继续include ModuleA。同样,它只是一样的简单步骤:
  13. include ModuleC首先检查include ModuleC的祖先链中是否已ModuleC。它不是。
  14. 因此,它创建了一个新类(让我们称之为ClassA)。
  15. 它使〚ModuleC′〛〚ModuleA′〛的当前超类)成为ClassA
  16. 的超类
  17. 它使〚ModuleC′〛成为〚ModuleC′〛的超类。此时,祖先链如下所示:ClassAClassA〚ModuleC′〛〚ModuleA′〛〚ModuleB′〛Object→{{1 }}
  18. 现在它只是按照记录的顺序重复Kernel中记录的每个模块的所有内容。
  19. 首先,Ruby会检查BasicObject的祖先是否已经在ModuleC的祖先链中。它是。所以它不会再次获得ModuleB
  20. 我们已经完成了!
  21. ClassA的祖先链如下所示:includeClassAClassA〚ModuleC′〛〚ModuleA′〛→{ {1}}→〚ModuleB′〛

    所以,重复一下你的问题:

      

    将调用哪个初始化?来自moduleA还是来自moduleC?

    Object只需要一步一步提升&#34;祖先链。由于我们在KernelBasicObject将走向祖先链,从super的超类开始,直到找到另一个具有相同名称的方法。因此,它将从ClassA#initialize开始,它确实找到super方法并运行它。

      

    如果我想从ClassA实例调用〚ModuleC′〛该怎么办?

    嗯,那是我们刚刚做的,不是吗?只需致电initialize

    或者你的意思是没有还打电话给ModuleC#initialize?在这种情况下,您可以做的是直接抓住classA并将其绑定到super

    ClassA#initialize

    我知道很多Ruby教程都会让所有这些听起来非常复杂。但它不是。 ModuleC#initialize使得包含在其中包含的任何内容的超类的模块。期。它真的 简单。 Mixin继承只是继承。其他一切,方法查找,常量查找,instA,无论如何,它只是一样。

答案 1 :(得分:1)

要回答“将调用哪个初始化?来自moduleA或来自moduleC?”的问题,它是intialize module的{​​{1}} - 最后一个。这是Ruby中的一般规则。

你的例子确实让我思考了一下,即使你这样做只是为了好玩。我对如何实现你想要的东西略有不同。

1)创建一个include共有的模块,用于定义执行module方法工作的proc。这里的一个缺点是我正在改变常量。

initialize

2。)与上面类似,但避免常数。尽管如此,仍然使用module C INIT = [] end module A include C INIT << ->{puts "initialize() of Module A"} end module B include C INIT << ->{puts "initialize() of Module B"} end class D include A include B def initialize # Note that you don't need the 'C::' part here, # but it is safer. C::INIT.each(&:call) puts "initialize of class D" end end d = D.new

proc

顺便说一下,“在以后执行module C @@initializers = [] def add_initializer(proc) @@initializers << proc end def initializers @@initializers end end module A extend C add_initializer(->{puts "initialize() of Module A"}) end module B extend C add_initializer(->{puts "initialize() of Module B"}) end class D include A include B include C def initialize initializers.each(&:call) puts "initialize of class D" end end d = D.new s的注册代码”模式在Rails中非常突出。

修改

如果你想做一些比在proc体内添加puts更复杂的事情,你可以在proc的体内调用一个方法提升你想要的。

我已将proc的定义更改为:

module A