这是完整的故事,解释了理解为什么模块包含在 Ruby 中的工作方式所需的元编程概念。
包含模块时会发生什么?
将模块包含到类中会将模块添加到类的祖先中。ancestors
您可以通过调用其方法查看任何类或模块的祖先:
module M
def foo; "foo"; end
end
class C
include M
def bar; "bar"; end
end
C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
# ^ look, it's right here!
当您在 的实例上调用方法时C
,Ruby 将查看此祖先列表的每个项目,以便找到具有提供名称的实例方法。由于我们包含M
在C
中,M
现在是 的祖先C
,所以当我们调用foo
的实例时C
,Ruby 会在 中找到该方法M
:
C.new.foo
#=> "foo"
请注意,包含不会将任何实例或类方法复制到类- 它只是向类添加一个“注释”,它还应该在包含的模块中查找实例方法。
我们模块中的“类”方法呢?
因为包含只改变了实例方法的分派方式,所以将一个模块包含到一个类中只会使其实例方法在该类上可用。模块中的“类”方法和其他声明不会自动复制到类中:
module M
def instance_method
"foo"
end
def self.class_method
"bar"
end
end
class C
include M
end
M.class_method
#=> "bar"
C.new.instance_method
#=> "foo"
C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
Ruby 如何实现类方法?
在 Ruby 中,类和模块是普通对象——它们是类Class
和Module
. 这意味着您可以动态创建新类,将它们分配给变量等:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
同样在 Ruby 中,您可以在对象上定义所谓的单例方法。这些方法作为新的实例方法添加到对象的特殊隐藏单例类中:
obj = Object.new
# define singleton method
def obj.foo
"foo"
end
# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]
但是类和模块不也只是普通的对象吗?事实上他们是!这是否意味着他们也可以有单例方法?是的,它确实!这就是类方法的诞生方式:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
或者,更常见的定义类方法的方法是self
在类定义块中使用,它指的是正在创建的类对象:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
如何在模块中包含类方法?
正如我们刚刚建立的,类方法实际上只是类对象的单例类上的实例方法。这是否意味着我们可以只在单例类中包含一个模块来添加一堆类方法?是的,它确实!
module M
def new_instance_method; "hi"; end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
self.singleton_class.include M::ClassMethods
end
HostKlass.new_class_method
#=> "hello"
这一self.singleton_class.include M::ClassMethods
行看起来不太好,所以 Ruby 添加了Object#extend
,它的作用相同——即在对象的单例类中包含一个模块:
class HostKlass
include M
extend M::ClassMethods
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ there it is!
将extend
呼叫移动到模块中
前面的示例不是结构良好的代码,原因有两个:
- 我们现在必须在定义中调用和调用以 正确包含我们的模块。如果您必须包含许多类似的模块,这可能会变得非常麻烦。
include
extend
HostClass
HostClass
直接引用M::ClassMethods
,这是模块的一个实现细节M
,HostClass
不需要知道或关心。
那么这个怎么样:当我们include
在第一行调用时,我们以某种方式通知模块它已被包含,并且还给它我们的类对象,以便它可以调用extend
自己。这样,如果需要,添加类方法是模块的工作。
这正是特殊self.included
方法的用途。每当模块包含在另一个类(或模块)中时,Ruby 会自动调用此方法,并将宿主类对象作为第一个参数传入:
module M
def new_instance_method; "hi"; end
def self.included(base) # `base` is `HostClass` in our case
base.extend ClassMethods
end
module ClassMethods
def new_class_method; "hello"; end
end
end
class HostKlass
include M
def self.existing_class_method; "cool"; end
end
HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
# ^ still there!
当然,添加类方法并不是我们在self.included
. 我们有类对象,所以我们可以在它上面调用任何其他(类)方法:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end