从 Ruby 中的模块/mixin 继承类方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10692961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Inheriting class methods from modules / mixins in Ruby
提问by Boris Stitnicky
It is known that in Ruby, class methods get inherited:
众所周知,在 Ruby 中,类方法会被继承:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
However, it comes as a surprise to me that it does not work with mixins:
然而,令我惊讶的是它不适用于 mixin:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
I know that #extend method can do this:
我知道 #extend 方法可以做到这一点:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
But I am writing a mixin (or, rather, would like to write) containing both instance methods and class methods:
但是我正在编写一个包含实例方法和类方法的 mixin(或者更确切地说,想要编写):
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Now what I would like to do is this:
现在我想做的是:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
I want A, B inherit both instance and class methods from Commonmodule. But, of course, that does not work. So, isn't there a secret way of making this inheritance work from a single module?
我希望 A、B 从Common模块继承实例和类方法。但是,当然,这是行不通的。那么,难道没有一种秘密方法可以使这种继承从单个模块工作吗?
It seems inelegant to me to split this into two different modules, one to include, the other to extend. Another possible solution would be to use a class Commoninstead of a module. But this is just a workaround. (What if there are two sets of common functionalities Common1and Common2and we really need to have mixins?) Is there any deep reason why class method inheritance does not work from mixins?
将其分成两个不同的模块,一个用于包含,另一个用于扩展,这对我来说似乎不雅。另一种可能的解决方案是使用类Common而不是模块。但这只是一种解决方法。(如果有两组通用功能Common1,Common2并且我们真的需要使用 mixin 怎么办?)为什么类方法继承不能从 mixins 工作有什么深层原因?
回答by Sergio Tulentsev
A common idiom is to use includedhook and inject class methods from there.
一个常见的习惯用法是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"
回答by Máté Solymosi
Here is the full story, explaining the necessary metaprogramming concepts needed to understand why module inclusion works the way it does in Ruby.
这是完整的故事,解释了理解为什么模块包含在 Ruby 中的工作方式所需的必要元编程概念。
What happens when a module is included?
当包含一个模块时会发生什么?
Including a module into a class adds the module to the ancestorsof the class. You can look at the ancestors of any class or module by calling its ancestorsmethod:
将模块包含到类中,会将模块添加到类的祖先中。您可以通过调用其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!
When you call a method on an instance of C, Ruby will look at every item of this ancestor list in order to find an instance methodwith the provided name. Since we included Minto C, Mis now an ancestor of C, so when we call fooon an instance of C, Ruby will find that method in M:
当您在 的实例上调用方法时C,Ruby 将查看此祖先列表的每一项,以便找到具有提供名称的实例方法。由于我们包含Minto C,M现在是 的祖先C,因此当我们调用foo的实例时C,Ruby 会在M以下位置找到该方法:
C.new.foo
#=> "foo"
Note that the inclusion does not copy any instance or class methods to the class– it merely adds a "note" to the class that it should also look for instance methods in the included module.
请注意,包含不会将任何实例或类方法复制到类中——它只是向类添加了一个“注释”,即它也应该在包含的模块中查找实例方法。
What about the "class" methods in our module?
我们模块中的“类”方法怎么样?
Because inclusion only changes the way instance methods are dispatched, including a module into a class only makes its instance methods availableon that class. The "class" methods and other declarations in the module are not automatically copied to the class:
因为包含只会改变实例方法的调度方式,所以将模块包含到类中只会使其实例方法在该类上可用。模块中的“类”方法和其他声明不会自动复制到类中:
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
How does Ruby implement class methods?
Ruby 如何实现类方法?
In Ruby, classes and modules are plain objects – they are instances of the class Classand Module. This means that you can dynamically create new classes, assign them to variables, etc.:
在 Ruby 中,类和模块是普通对象——它们是类Class和Module. 这意味着您可以动态创建新类,将它们分配给变量等:
klass = Class.new do
def foo
"foo"
end
end
#=> #<Class:0x2b613d0>
klass.new.foo
#=> "foo"
Also in Ruby, you have the possibility of defining so-called singleton methodson objects. These methods get added as new instance methods to the special, hidden singleton classof the object:
同样在 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]
But aren't classes and modules just plain objects as well? In fact they are! Does that mean that they can have singleton methods too? Yes, it does! And this is how class methods are born:
但是类和模块不也只是普通对象吗?事实上他们是!这是否意味着他们也可以有单例方法?是的,它确实!这就是类方法的诞生方式:
class Abc
end
# define singleton method
def Abc.foo
"foo"
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
Or, the more common way of defining a class method is to use selfwithin the class definition block, which refers to the class object being created:
或者,更常见的定义类方法的方法是self在类定义块中使用,它指的是正在创建的类对象:
class Abc
def self.foo
"foo"
end
end
Abc.singleton_class.instance_methods(false)
#=> [:foo]
How do I include the class methods in a module?
如何在模块中包含类方法?
As we just established, class methods are really just instance methods on the singleton class of the class object. Does this mean that we can just include a module into the singleton classto add a bunch of class methods? Yes, it does!
正如我们刚刚建立的,类方法实际上只是类对象的单例类上的实例方法。这是否意味着我们可以只在单例类中包含一个模块来添加一堆类方法?是的,它确实!
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"
This self.singleton_class.include M::ClassMethodsline does not look very nice, so Ruby added Object#extend, which does the same – i.e. includes a module into the singleton class of the object:
这一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!
Moving the extendcall into the module
将extend调用移动到模块中
This previous example is not well-structured code, for two reasons:
前面的示例不是结构良好的代码,原因有二:
- We now have to call both
includeandextendin theHostClassdefinition to get our module included properly. This can get very cumbersome if you have to include lots of similar modules. HostClassdirectly referencesM::ClassMethods, which is an implementation detailof the moduleMthatHostClassshould not need to know or care about.
- 我们现在必须在定义中同时调用
include和extend以HostClass正确包含我们的模块。如果您必须包含许多类似的模块,这会变得非常麻烦。 HostClass直接引用M::ClassMethods,这是一个实现细节的模块M是HostClass不应该需要知道或关心。
So how about this: when we call includeon the first line, we somehow notify the module that it has been included, and also give it our class object, so that it can call extenditself. This way, it's the module's job to add the class methods if it wants to.
那么如何:当我们include在第一行调用时,我们以某种方式通知模块它已被包含,并且还给它我们的类对象,以便它可以调用extend自己。这样,如果需要,模块的工作就是添加类方法。
This is exactly what the special self.includedmethodis for. Ruby automatically calls this method whenever the module is included into another class (or module), and passes in the host class object as the first argument:
这正是特殊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!
Of course, adding class methods is not the only thing we can do in self.included. We have the class object, so we can call any other (class) method on it:
当然,添加类方法并不是我们在self.included. 我们有类对象,因此我们可以在其上调用任何其他(类)方法:
def self.included(base) # `base` is `HostClass` in our case
base.existing_class_method
#=> "cool"
end
回答by Franklin Yu
As Sergio mentioned in comments, for guys who are already in Rails (or don't mind depending on Active Support), Concernis helpful here:
正如 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
回答by Bryan Colvin
You can have your cake and eat it too by doing this:
你可以通过这样做来吃蛋糕和吃蛋糕:
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"]
If you intend to add instance, and class variables, you will end up pulling out your hair as you will run into a bunch of broken code unless you do it this way.
如果你打算添加实例和类变量,你最终会陷入困境,因为你会遇到一堆损坏的代码,除非你这样做。

