为什么一个模块的单例方法在混入后的下游特异类中不可见?

9

我了解常规的方法查找路径,即类、超类/模块,一直到BasicObject。我认为这对于链的单件版本也是正确的,但当您在元链中混入一个模块时,似乎并非如此。在下面的例子中,当我将Automobile模块包含在Vehicle的特有类中时,调用了Automobile模块的banner方法而不是其单件版本,如果有人能解释一下这是为什么,我会很感激。

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
2个回答

9

首先,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"

首先感谢您的回复。我明白了,当我包含一个模块时,它的特殊类方法会被忽略,只有它的实例方法会暴露给类。我真正想要理解的是这种行为背后的原理是什么? - saihgala
这只是一种简单的一致行为。但是我真的不知道为什么您不能include类方法。 - Patrick Oscity
也许我们从不同的角度看待这个问题,但是当你扩展一个类时,它的实例和特殊类方法都会暴露给子类(例如在这个示例中,Car可以访问Vehicle.bannerVehicle.new.banner)。因此,阻止Automobile单例banner方法被Vehicle继承似乎对我来说是不一致的。 - saihgala
是的,但继承不同!按定义,include仅包括实例方法。类继承两者都包括。请记住,模块只是将其他代码分组的机制。 - Patrick Oscity
搜索档案时,我在Matz的话中发现 - “Mix-in用于多种目的,其中一些可能会受到继承类方法的阻碍。例如,当包含Math模块时,我不想泄漏内部方法。我并不反对某种包含形式也继承类方法,但它应该与当前的#include行为分离开来。” - saihgala
明白了:类(Class)是一个模块(Module),并且类(Class)有比模块(Module)更多的功能。 - William Entriken

2
首先,类是一个对象,就像其他对象一样,它也有自己的超类; 其次,Eigenclass本身是一个普通类,只是匿名的并且有点隐形; 第三,派生类的eigenclass的超类是基类的eigenclass; 第四,include包括所包含模块的实例方法(而不是单例方法),使它们成为接收者类对象的实例方法。
在您的示例中有两个平行的继承链
汽车 < 车辆 < ...
汽车的eigenclass < 车辆的eigenclass < 汽车公司 < ...
在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在特殊类的继承链中。但是遗憾的是,ancestor方法并没有返回不可见的特殊类,尽管它们实际上在第二个链中。


我喜欢你在“Object”上的“eigenclass”定义。而且你应该把“ancestor”改成“ancestors”。 - saihgala
将上述的一个特殊类方法添加到Object类中,对于检查来说是非常有帮助的。令人惊讶的是,这个功能还没有默认地加入到Ruby中。 - fontno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接