inheritanceRuby中的modules / mixins的类方法
众所周知,在Ruby中,类方法被inheritance:
class P def self.mm; puts 'abc' end end class Q < P; end Q.mm # works 然而,对于我来说,它并不适用于mixin:
 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模块inheritance实例和类方法。 但是,当然,这是行不通的。 那么,是不是有一个秘密的方法来使这个inheritance工作从一个单一的模块? 
 对我来说,把它分成两个不同的模块,一个包括另一个模块。 另一个可能的解决scheme是使用类Common而不是模块。 但这只是一个解决方法。  (如果有两套常见的functionCommon1和Common2 ,我们真的需要mixin吗?)为什么类方法inheritance不能从mixins中工作? 
 一个常见的习惯是使用included钩子和从那里注入类的方法。 
 module Foo def self.included base base.send :include, InstanceMethods base.extend ClassMethods end module InstanceMethods def bar1 'bar1' end end module ClassMethods def bar2 'bar2' end end end class Test include Foo end Test.new.bar1 # => "bar1" Test.bar2 # => "bar2" 
这里是完整的故事,解释了必要的元编程概念,了解为什么模块包含在Ruby中的工作方式。
包含模块时会发生什么?
 将模块包含到类中会将该模块添加到类的祖先中。 您可以通过调用其ancestors方法来查看任何类或模块的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会查看这个祖先列表中的每一个项目,以便find一个提供名字的实例方法 。 因为我们把M包含到C ,所以M现在是C的祖先,所以当我们在C一个实例上调用foo时,Ruby会在Mfind这个方法: 
 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类的实例。 这意味着你可以dynamic地创build新的类,将它们赋值给variables等。 
 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级方法的诞生:
 class Abc end # define singleton method def Abc.foo "foo" end Abc.singleton_class.instance_methods(false) #=> [:foo] 
 或者,更常见的定义类方法的方法是在类定义块中使用self ,它指向正在创build的类对象: 
 class Abc def self.foo "foo" end end Abc.singleton_class.instance_methods(false) #=> [:foo] 
如何将类方法包含在模块中?
就像我们刚刚build立的那样,类方法实际上只是类对象的单例类中的实例方法。 这是否意味着我们可以在单例类中添加一个模块来添加一堆类方法? 是的,它确实!
 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 ,它也是这样做的 – self.singleton_class.include M::ClassMethods一个模块包含到对象的单例类中: 
 class HostKlass include M extend M::ClassMethods end HostKlass.singleton_class.included_modules #=> [M::ClassMethods, Kernel] # ^ there it is! 
 将extend调用移入模块 
前面的例子不是结构良好的代码,原因有两个:
-  我们现在必须在HostClass定义中调用include和extend来正确地包含我们的模块。 如果你必须包含许多类似的模块,这可能会变得非常麻烦。
-   HostClass直接引用M::ClassMethods,它是HostClass不需要知道或关心的模块M的实现细节 。
 那么怎么样呢:当我们在第一行调用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 
 正如Sergio在评论中提到的那样,对于已经在Rails中的人(或者不介意依赖Active Support ), Concern在这里很有帮助: 
 require 'active_support/concern' module Common extend ActiveSupport::Concern def instance_method puts "instance method here" end class_methods do def class_method puts "class method here" end end end class A include Common end