【问题标题】:Why does extend method on class an instance works differently with inheritance?为什么类实例上的扩展方法与继承的工作方式不同?
【发布时间】:2019-09-23 06:17:21
【问题描述】:
module B
  def a
    print 'B'
    super
  end
end

class A
  extend B

  def a
    print "A"
  end

  def self.a
    print "A"
  end
end

a = A.new

a.extend B
puts a.a # => BA
puts A.a # => A

为什么 Kernel#extend 方法对类对象和类实例对象的工作方式不同?如果我们扩展一个实例,它看起来像是在继承链中预先添加模块,但如果我们扩展一个类,则将模块放在类之上。

【问题讨论】:

    标签: ruby oop inheritance


    【解决方案1】:
    A.singleton_class.ancestors
       #=> [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module,
       #    Object, Kernel, BasicObject] 
    A.method(:a).owner
       #=> #<Class:A> 
    

    所以我们不应该感到惊讶

    A.a 
    

    打印"A",而不是"B"

    现在让我们考虑在A 的实例上扩展B

    aa = A.new
    aa.extend B
    
    aas = aa.singleton_class
      #=> #<Class:#<A:0x00005b19441bf6a8>> 
    aas.methods.include?(:a)
      #=> true
    aa.method(:a).owner
      #=> B 
    aas.superclass
      #=> A
    m = aa.method(:a).super_method
      #=> #<Method: A#a> 
    m.owner
      #=> A 
    

    因此,

    aa.a
    BA
    

    首先调用定义在aa 的单例类上的方法a,打印字母"B",然后调用它的超类A 来执行A.a,该方法为A::a ,导致它打印"A"

    【讨论】:

      【解决方案2】:

      我先介绍几个概念。

      首先,使用def self.a定义类方法与在类的单例类上定义方法相同:

      class C
        def self.a; end
      
        class << self
          def b; end
        end
      end
      
      C.method(:a) # => #<Method: C.a>
      C.method(:b) # => #<Method: C.b>
      

      此外,对象上的方法是该对象单例类上的实例方法:

      C.singleton_class.instance_method(:a) # => #<UnboundMethod: #<Class:C>#a>
      C.singleton_class.instance_method(:b) # => #<UnboundMethod: #<Class:C>#b>
      

      如果你看看我们上面是如何定义#b的,你会发现我们没有在它前面加上self,因此这只是一个实例方法。

      接下来,#extend 与单例类上的#include 相同:

      module M; end
      
      class C1
        extend M
      end
      
      class C2
        class << self
          include M
        end
      end
      
      C1.ancestors # => [#<Class:C2>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
      C2.ancestors # => [#<Class:C1>, M, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
      

      注意M 现在是C1C2 祖先的一部分。

      包含(或扩展)M 也可以通过以下方式实现:

      C1.extend M
      C2.singleton_class.include M
      

      最后,注意当我们 #include 一个模块时祖先会发生什么:

      module M1; end
      module M2; end
      class C; end
      
      C.include M1
      C.ancestors # => [C, M1, Object, Kernel, BasicObject]
      
      C.include M2
      C.ancestors # => [C, M2, M1, Object, Kernel, BasicObject]
      

      每个#include 导致模块被插入到祖先链中的接收者(在本例中为C)之后。

      现在让我们看看你的定义(省略正文):

      module B; end
      
      class A
        extend B
      end
      

      请记住,#extend#singleton_class 上的 #include 相同。因此,我们可以改写如下:

      module B; end
      class A; end
      A.singleton_class.include B
      

      单例类的祖先现在在第一项之后有B,这是A的单例类,其中定义了类方法(请记住,类方法因此只是该类的单例类上的实例方法有问题):

      A.singleton_class.ancestors # => [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
      

      继续代码的第二部分:

      a = A.new
      a.extend B
      

      使用#include重写它:

      a = A.new
      a.singleton_class.include B
      

      让我们检查祖先:

      a.singleton_class.ancestors # => [#<Class:#<A:0x00007f83e714be88>>, B, A, Object, Kernel, BasicObject]
      

      同样,#include 将模块放在祖先链中的第一个元素之后,导致BA 之前。

      这意味着当发送#aa(即a.a)时,它将寻找第一个响应#a 的祖先,在这种情况下是B。然后B 将调用super,它将继续沿着祖先链找到响应#aA

      现在A.a 会有所不同。记住A的单例类的祖先:

      A.singleton_class.ancestors # => [#<Class:A>, B, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
      

      注意B 是如何出现在#&lt;Class:A&gt; 之后的。 #&lt;Class:A&gt; 已经响应#a,这是A 上的类方法。因为该方法不调用super,所以永远不会调用B#a。因此,您不会得到相同的输出。

      如果你想在#&lt;Class:A&gt; 之前有B,你必须在A 的单例类前面加上B。 #prepend 在祖先链的最开始插入一个对象,这与 #include 不同,后者将其插入到第一项之后(您必须删除代码中的 extend B 才能使其正常工作,否则如果 B 是已经是祖先了):

      A.singleton_class.prepend B
      A.singleton_class.ancestors # => [B, #<Class:A>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
      

      现在调用A.a 将产生与a.a 相同的结果,即打印BA

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-11
        • 2023-03-29
        • 2012-09-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-10
        • 2017-03-16
        相关资源
        最近更新 更多