众所周知,在Ruby中,类方法得到了继承:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
然而,令我惊讶的是它不适用于mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
我知道#extend方法可以做到这一点:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
但我正在编写一个包含实例方法和类方法的mixin(或者,更愿意写):
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
现在我想做的是:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
我想要A,B从 Common
模块继承实例和类方法 . 但是,当然,这不起作用 . 那么,是不是有一个秘密的方法使这个继承从单个模块工作?
我把它分成两个不同的模块似乎不太优雅,一个包含,另一个包括扩展 . 另一种可能的解决方案是使用类 Common
而不是模块 . 但这只是一种解决方法 . (如果有两组常见功能 Common1
和 Common2
并且我们真的需要mixins?怎么办?)有没有深层次的原因为什么类方法继承不能用mixins工作?
4 回答
一个常见的习惯用法是使用
included
hook并从那里注入类方法 .以下是完整的故事,解释了理解为什么模块包含在Ruby中的工作方式所需的元编程概念 .
包含模块时会发生什么?
将模块包含到类中会将模块添加到类的祖先中 . 您可以通过调用
ancestors
方法查看任何类或模块的祖先:当您在
C
的实例上调用方法时,Ruby将查看此祖先列表的每个项目,以便找到具有提供的名称的 instance method . 由于我们将M
包含在C
中,M
现在是C
的祖先,所以当我们在C
的实例上调用foo
时,Ruby会在M
中找到该方法:注意 the inclusion does not copy any instance or class methods to the class - 它只是在类中添加"note",它也应该在包含的模块中查找实例方法 .
我们模块中的“类”方法怎么样?
因为包含仅更改实例方法的调度方式,包括将该模块放入该类的类 only makes its instance methods available 中 . 模块中的"class"方法和其他声明不会自动复制到类中:
Ruby如何实现类方法?
在Ruby中,类和模块是普通对象 - 它们是类
Class
和Module
的实例 . 这意味着您可以动态创建新类,将它们分配给变量等:同样在Ruby中,您可以在对象上定义所谓的 singleton methods . 这些方法作为新实例方法添加到对象的特殊隐藏 singleton class :
但是类和模块不仅仅是普通的对象吗?事实上他们是!这是否意味着他们也可以使用单例方法?是的,它确实!这就是类方法的诞生方式:
或者,更常见的定义类方法的方法是在类定义块中使用
self
,它引用正在创建的类对象:如何在模块中包含类方法?
正如我们刚刚 Build 的那样,类方法实际上只是类对象的singleton类上的实例方法 . 这是否意味着我们可以只添加一堆类方法 include a module into the singleton class ?是的,它确实!
这个
self.singleton_class.include M::ClassMethods
行看起来不太好,所以Ruby添加了Object#extend,它也是这样做的 - 即包含一个模块到对象的单例类中:将扩展调用移动到模块中
前面的示例不是结构良好的代码,原因有两个:
我们现在必须在
HostClass
定义中同时调用include
和extend
才能正确包含我们的模块 . 如果你必须包含许多类似的模块,这可能会变得非常麻烦 .HostClass
直接引用M::ClassMethods
,这是M
模块的实现细节,HostClass
不应该知道或关心 .那么怎么样:当我们在第一行调用
include
时,我们以某种方式通知模块它已被包含,并且还给它我们的类对象,以便它可以调用extend
本身 . 这样,它就可以添加类方法,如果它想要的话 .这正是 special self.included method 的用途 . 只要模块包含在另一个模块中,Ruby就会自动调用此方法class(或module),并将宿主类对象作为第一个参数传递:
当然,添加类方法并不是我们在_1775700中唯一能做的事情 . 我们有类对象,所以我们可以调用任何其他(类)方法:
正如Sergio在评论中提到的那样,对于已经在Rails中的人(或者不介意依赖于Active Support),Concern在这里很有帮助:
这样做你可以吃蛋糕并吃掉它:
如果你打算添加实例和类变量,你最终会拔掉你的头发,因为你会遇到一堆破碎的代码,除非你这样做 .