据了解,在Ruby中,类方法得到继承:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
然而,这是一个意外,我认为它不与混入工作:
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
,我们真的需要有混入?)有什么深层原因类方法的继承不会混入工作?
一个常见的成语是使用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
的方法:
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
,红宝石会看看这个祖先列表的每一个项目,以便找到与所提供的名称的实例方法 。 因为我们包括M
到C
, M
是现在的祖先C
,所以当我们调用foo
上的一个实例C
,红宝石会发现方法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
方法是。 红宝石自动调用此方法每当模块被包括到另一个类(或模块),并通过在宿主类对象作为第一个参数:
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
由于塞尔吉奥的意见,对于球员谁已经在铁轨(或者根据不介意提到主动支持 ), 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
你可以有你的蛋糕,通过这样吃太:
module M
def self.included(base)
base.class_eval do # do anything you would do at class level
def self.doit #class method
@@fred = "Flintstone"
"class method doit called"
end # class method define
def doit(str) #instance method
@@common_var = "all instances"
@instance_var = str
"instance method doit called"
end
def get_them
[@@common_var,@instance_var,@@fred]
end
end # class_eval
end # included
end # module
class F; end
F.include M
F.doit # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]
如果要添加实例,类变量,你最终会拉你的头发,除非你做这种方式你会遇到一堆断码。