众所周知,在Ruby中,类方法得到了继承:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
Run Code Online (Sandbox Code Playgroud)
然而,令我惊讶的是它不适用于mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Run Code Online (Sandbox Code Playgroud)
我知道#extend方法可以做到这一点:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Run Code Online (Sandbox Code Playgroud)
但我正在编写一个包含实例方法和类方法的mixin(或者,更愿意写):
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Run Code Online (Sandbox Code Playgroud)
现在我想做的是:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Run Code Online (Sandbox Code Playgroud)
我想要A,B从Common
模块继承实例和类方法.但是,当然,这不起作用.那么,是不是有一个秘密的方法使这个继承从单个模块工作?
我把它分成两个不同的模块似乎不太优雅,一个包含,另一个包括扩展.另一种可能的解决方案是使用类Common
而不是模块.但这只是一种解决方法.(如果有两组常见功能Common1
并且Common2
我们确实需要mixins怎么办?)有没有深层次的原因为什么类方法继承不能用mixins工作?
Ser*_*sev 162
一个常见的习惯用法是included
从那里使用hook和inject类方法.
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"
Run Code Online (Sandbox Code Playgroud)
Mát*_*osi 36
以下是完整的故事,解释了理解为什么模块包含在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!
Run Code Online (Sandbox Code Playgroud)
当您在实例上调用方法时C
,Ruby将查看此祖先列表的每个项目,以便查找具有提供的名称的实例方法.因为我们包括M
进C
,M
是现在的祖先C
,所以当我们调用foo
上的一个实例C
,红宝石会发现方法M
:
C.new.foo
#=> "foo"
Run Code Online (Sandbox Code Playgroud)
请注意,包含不会将任何实例或类方法复制到类中 - 它只是向类添加"注释",它还应该在包含的模块中查找实例方法.
因为列入只是改变了方法实例方法分派,其中包括一个模块在一个类只能使可用的实例方法对类.模块中的"类"方法和其他声明不会自动复制到类中:
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
Run Code Online (Sandbox Code Playgroud)
在Ruby中,类和模块是普通对象 - 它们是类Class
和实例Module
.这意味着您可以动态创建新类,将它们分配给变量等:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
Run Code Online (Sandbox Code Playgroud)
同样在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]
Run Code Online (Sandbox Code Playgroud)
但是类和模块不仅仅是普通的对象吗?事实上他们是!这是否意味着他们也可以使用单例方法?是的,它确实!这就是类方法的诞生方式:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Run Code Online (Sandbox Code Playgroud)
或者,定义类方法的更常见方法是self
在类定义块中使用,该块引用正在创建的类对象:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Run Code Online (Sandbox Code Playgroud)
正如我们刚刚建立的那样,类方法实际上只是类对象的singleton类上的实例方法.这是否意味着我们可以在singleton类中包含一个模块来添加一堆类方法?是的,它确实!
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"
Run Code Online (Sandbox Code Playgroud)
这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!
Run Code Online (Sandbox Code Playgroud)
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!
Run Code Online (Sandbox Code Playgroud)
当然,添加类方法并不是我们唯一可以做的事情self.included
.我们有类对象,所以我们可以调用任何其他(类)方法:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end
Run Code Online (Sandbox Code Playgroud)
正如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
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
37508 次 |
最近记录: |