【问题标题】:Redefining class in Ruby seems to include wrong module在 Ruby 中重新定义类似乎包含错误的模块
【发布时间】:2017-07-20 18:51:58
【问题描述】:

我来自 Python 背景,我试图了解 Ruby 包含与 Python 的多重继承相比如何。具体来说,我用相同的方法设置了两个模块来看看super 是如何工作的。

我添加了Mod,然后是Mod2,然后又是Mod。但是,A 似乎仍在引用 Mod2

module Mod
  def foo(x)
    x**2
  end
end
module Mod2
  def foo(x)
    x*2
  end
end

class A
  include Mod
  def foo(x)
    super(x) + 1
  end
end
A.new.foo(5) == 26 # true

class A
  include Mod2
  def foo(x)
    super(x) + 1
  end
end
A.new.foo(5) == 11 # true

class A
  include Mod
  def foo(x)
    super(x) + 1
  end
end
A.new.foo(5) == 26 # false. Is 11

为什么第三个A.new.foo(5) 不返回26

【问题讨论】:

    标签: ruby class module include


    【解决方案1】:

    我认为您可能缺少的是第二个class A ... end 并没有重新定义课程,而是添加了课程。您可以在 ruby​​ 中多次“打开”一个类并(重新)定义您想要的任何内容。那,你似乎已经想通了,那就是include 或多或少只是将模块中的代码放到类定义中的那个位置,除非该模块已经包含在这种情况下它什么都不做。

    您可以在任何课程中执行此操作(无论是否可取)

    class Hash
      def to_a
        'lol you probably wanted an array here'
      end
    end
    {foo: :bar}.to_a # Returns above string instead of [:foo, :bar]
    

    【讨论】:

      【解决方案2】:

      在 Python 中定义 class A(object) 创建一个具有 typetypeA__name__ 的对象,然后将该对象分配给标签 A。第二个class A(object) 定义创建一个全新的type 对象,然后将其放入标签A。

      ruby 中的类不存储在标签中,可以打开它们以添加更多功能。 2.days 就是一个很好的例子。 Rails 在整数上定义了days。如果没有 Rails,2.days 会失败。

      上面的3个类定义大致等价于:

      class A
        include Mod
        def foo(x)
          super(x) + 1
        end
      
        include Mod2
        def foo(x)
          super(x) + 1
        end
      
        include Mod
        def foo(x)
          super(x) + 1
        end
      end
      

      当 ruby​​ 看到第二个 include Mod 时,它知道 Mod 已经包含在类中并跳过它,将 Mod2 中的 foo 作为最新定义。

      如果您从以后的定义中删除 fooA 仍然可以访问它:

      class A
        include Mod
        def foo(x)
          super(x) + 1
        end
      end
      A.new.foo(5) == 26 # true
      
      class A
        include Mod2
      end
      A.new.foo(5) == 11 # true
      
      class A
        include Mod
      end
      A.new.foo(5) == 11
      

      【讨论】:

        【解决方案3】:

        Ruby 通过一次一个类/模块搜索类/模块的祖先链来找到super,直到找到它正在寻找的方法的定义。有点像一辆有很多站的公共汽车,寻找合适的下车地点,但总是一有机会就下车。如果它一直到BasicObject 并且没有发现它停止,它将引发NoMethodError

        当您包含一个模块时,您会将其添加到包含类/模块上方的祖先链中。你可以通过调用Module.ancestors按顺序找到祖先。你可以多次包含同一个模块,但它只会被添加到祖先链中一次。考虑一下:

        module Mod
          def foo(x)
            x**2
          end
        end
        
        module Mod2
          def foo(x)
            x*2
          end
        end
        
        class A
          include Mod
          def foo(x)
            super(x) + 1
          end
        end
        
        A.ancestors # => [A, Mod, Object, Kernel, BasicObject]
        
        class A
          include Mod2
          def foo(x)
            super(x) + 1
          end
        end
        
        A.ancestors # => [A, Mod2, Mod, Object, Kernel, BasicObject]
        
        class A
          include Mod
          def foo(x)
            super(x) + 1
          end
        end
        
        A.ancestors # => [A, Mod2, Mod, Object, Kernel, BasicObject]
        

        Mod 第二次被包含在祖先链中,所以没有新的事情发生。它保持不变,super 仍然首先击中Mod2。简而言之,您不能通过包含已经存在的旧模块来“覆盖”较新的模块。不过,您还可以做其他事情。你可以定义一个Mod3,或者ModOverride,与foo 的定义相同,在Mod 中包含它,或者查看改进Ruby 2.0 How do I uninclude a module out from a module after including it?

        旁注

        正如其他答案所述,如果定义保持不变,则无需每次都重新定义 A#foo

        【讨论】:

          猜你喜欢
          • 2015-11-29
          • 2015-02-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多