为啥模块的单例方法在混合的下游特征类中不可见?

Posted

技术标签:

【中文标题】为啥模块的单例方法在混合的下游特征类中不可见?【英文标题】:Why a module's singleton method is not visible in downstream eigenclasses where it gets mixed?为什么模块的单例方法在混合的下游特征类中不可见? 【发布时间】:2012-10-27 11:34:50 【问题描述】:

我了解常规方法查找路径,即class, superclass/module, all the way up to BasicObject。我认为对于链的单例版本也是如此,但当您在元链中混合模块时似乎并非如此。如果有人能解释为什么在以下示例中调用 Automobile 模块的 banner 方法而不是它的单例版本,当我将此模块包含在 Vehicle 的 eigenclass 中时,我将不胜感激。

module Automobile
  def banner
    "I am a regular method of Automobile"
  end

  class << self
    def banner
      "I am a class method of Automobile"
    end
  end
end

class Vehicle 
  def banner
    "I am an instance method of Vehicle"
  end

  class << self
    include Automobile
    def banner
      puts "I am a class method of Vehicle"
      super
    end
  end
end

class Car < Vehicle
  def banner
    "I am an instance method of Car"
  end

  class << self
    def banner
      puts "I am a class method of Car"
      super
    end
  end
end

puts Car.banner

# I am a class method of Car
# I am a class method of Vehicle
# I am a regular method of Automobile

【问题讨论】:

【参考方案1】:

首先,类是一个对象,就像其他对象一样,它也有自己的超类; 其次,Eigenclass 本身是一个普通的类,只有匿名且有点不可见; 第三,派生类的eigenclass的超类是基类的eigenclass; 第四,include包含被包含模块的实例方法(不是单例方法),使其成为receiver类对象的实例方法。

您的示例中有两个并行的继承链

汽车

汽车的特征类

对irb做如下测试:

class Object
  def eigenclass
    class << self
      self
    end
  end
end

Car.ancestors # => [Car, Vehicle, Object, Kernel, BasicObject]
Car.eigenclass.ancestors # => [Automobile, Class, Module, Object, Kernel, BasicObject]
Vehicle.eigenclass.ancestors # => [Automobile, Class, Module, Object, Kernel, BasicObject]
Car.eigenclass.superclass.equal? Vehicle.eigenclass # => true

你看,Automobile 在 eigenclass 继承链中。但遗憾的是,ancestor 方法并没有返回不可见的特征类,但它们实际上是在第二条链中。

【讨论】:

我喜欢你在Object 上的eigenclass 定义。您应该将ancestor 更改为ancestors 像上面一样给Object添加一个eigenclass方法对检查很有帮助。很惊讶这并没有在默认情况下进入 Ruby【参考方案2】:

首先,include 不包括您可能期望的特征类方法。考虑:

module Foo
  class << self
    def do_something
      puts "Foo's eigenclass method does something"
    end
  end
end

module Bar
  include Foo
end

puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)

请注意,这与经典定义的类方法的行为一致:

module Foo
  def self.do_something
    puts "Foo's class method does something"
  end
end

module Bar
  include Foo
end

puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)

常见的做法是在子模块中定义类方法,然后在包含模块时触发对extend的调用:

module Foo
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def do_something
      puts "Foo::ClassMethod's instance method does something"
    end
  end
end

module Bar
  include Foo
end

puts Bar.do_something
# Foo::ClassMethod's instance method does something

要注意的第二件事是,您确实将Automobile 的实例方法包含在Vehicle 的特征类中,因此Automobile 的实例方法变成了Vehicle 的(特征)类方法。

您的Car 类基本上与这一切无关。这里唯一要注意的是,类继承也使类方法可用,而include 没有。示例:

class Foo
  def self.do_something
    puts "Foo's class method does something"
  end
end

class Bar < Foo
end

puts Bar.do_something
# "Foo's class method does something"

【讨论】:

首先感谢您的回复。我知道当我包含一个模块时,它的特征类方法被忽略,只有它的实例方法被暴露给类。我真正想了解的是这种行为背后的基本原理? 这只是一致的行为。但老实说,我不知道为什么你不能include 类方法。 也许我们从不同的角度来看它,但是当你扩展一个类时,它的实例以及 eigenclass 方法都会暴露给子类(在这个例子中,Car 可以访问 Vehicle.banner 作为以及Vehicle.new.banner)。因此,从 Vehicle 中保留汽车的单例 banner 方法对我来说似乎不一致。 可以,但是继承不一样! include 根据定义只包括实例方法。类继承两者兼而有之。请记住,模块只不过是一种将其他代码组合在一起的机制。 搜索我在 Matz 单词中找到的档案 - Mix-in is used for several purposes and some of them can be hindered by inheriting class methods, for example, I don't want to spill internal methods when including the Math module. I am not against for some kind of inclusion that inherits class methods as well, but it should be separated from the current #include behavior.

以上是关于为啥模块的单例方法在混合的下游特征类中不可见?的主要内容,如果未能解决你的问题,请参考以下文章

gcc 编译错误:模板类表中嵌套类 A ​​的成员在嵌套朋友类中不可见。为啥?

公共方法在另一个类中不可见

定义仅在某个模块/类中可见的方法

为啥提交在 github 上可见但在本地代表中不可见?

为啥同步融合 DomainUpDown 控件在 wpf 中不可见?

为啥 $ids 在 whereHas() 的回调中不可见?