Ruby 元类疯狂
Posted
技术标签:
【中文标题】Ruby 元类疯狂【英文标题】:Ruby metaclass madness 【发布时间】:2011-01-27 15:13:11 【问题描述】:我被困住了。我正在尝试动态定义一个类方法,但我无法理解 ruby 元类模型。考虑以下类:
class Example
def self.meta; (class << self; self; end); end
def self.class_instance; self; end
end
Example.class_instance.class # => Class
Example.meta.class # => Class
Example.class_instance == Example # => true
Example.class_instance == Example.meta # => false
显然这两个方法都返回一个 Class 的实例。但是这两个例子 不一样。他们也有不同的祖先:
Example.meta.ancestors # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors # => [Example, Object, Kernel]
区分元类和类实例有什么意义?
我发现,我可以send :define_method
到元类来动态定义一个方法,但是如果我尝试将它发送到类实例,它就不起作用了。至少我可以解决我的问题,但我仍然想了解它为什么会这样工作。
2010 年 3 月 15 日 13:40 更新
以下假设是否正确。
如果我有一个调用 self.instance_eval 并定义一个方法的实例方法,它只会影响该类的特定实例。 如果我有一个调用 self.class.instance_eval(与调用 class_eval 相同)并定义一个方法的实例方法,它将影响该特定类的所有实例,从而产生一个新的实例方法。 如果我有一个调用 instance_eval 并定义一个方法的类方法,它将为所有实例生成一个新的实例方法。 如果我有一个类方法,它在 meta/eigen 类上调用 instance_eval 并定义了一个方法,它将产生一个类方法。我认为它开始对我有意义。如果类方法中的 self 指向 eigen 类,那肯定会限制您的可能性。如果是这样,就不可能从类方法内部定义实例方法。对吗?
【问题讨论】:
【参考方案1】:使用instance_eval
动态定义单例方法很简单:
Example.instance_eval def square(n); n*n; end
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end"
Example.multiply(3,9) #=> 27
至于上面的区别,你混淆了两件事:
您定义的元类,在 Ruby 社区中称为 singelton 类 或 eigen 类。该单例类是您可以添加类(单例)方法的类。
至于您尝试使用class_instance
方法定义的类实例,只不过是类本身,为了证明这一点,只需尝试将实例方法添加到类Example
并检查class_instance
您定义的方法通过检查该方法的存在来返回类Example
本身:
class Example
def self.meta; (class << self; self; end); end
def self.class_instance; self; end
def hey; puts hey; end
end
Example.class_instance.instance_methods(false) #=> ['hey']
总之为你总结一下,当你想添加类方法时,只需将它们添加到那个元类。至于class_instance
这个方法没用,去掉就好。
无论如何我建议你阅读this post 以掌握一些Ruby反射系统的概念。
更新
我建议你阅读这篇好文章:Fun with Ruby's instance_eval and class_eval,
不幸的是,class_eval
和 instance_eval
令人困惑,因为它们以某种方式违背了它们的命名!
Use ClassName.instance_eval to define class methods.
Use ClassName.class_eval to define instance methods.
现在回答你的假设:
如果我有一个实例方法 调用 self.instance_eval 并定义一个 方法,只会影响 该类的特定实例。
是的:
class Foo
def assumption1()
self.instance_eval("def test_assumption_1; puts 'works'; end")
end
end
f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []
如果我有一个实例方法 调用 self.class.instance_eval (其中 与调用相同 class_eval) 并定义了一个方法 将影响所有实例 特定的类导致一个新的 实例方法。
没有instance_eval
在该上下文中将在类本身上定义单例方法(不是实例方法):
class Foo
def assumption2()
self.class.instance_eval("def test_assumption_2; puts 'works'; end")
end
end
f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]
为此,将instance_eval
替换为上面的class_eval
。
如果我有一个类方法调用 instance_eval 并定义了一个方法 将产生一个新的实例方法 适用于所有情况。
没有:
class Foo
instance_eval do
def assumption3()
puts 'works'
end
end
end
Foo.instance_methods(false) #=> []
Foo.singleton_methods(false) #=> ["assumption_3"]
这将创建单例方法,而不是实例方法。为此,将instance_eval
替换为上面的class_eval
。
如果我有一个类方法调用 元/特征类上的 instance_eval 并定义将导致的方法 一个类方法。
不,那会做这么复杂的东西,因为它会给单例类添加单例方法,我认为这不会有任何实际用途。
【讨论】:
有关为什么在instance_eval
中的def
定义类方法的更多信息,请参阅这篇文章yugui.jp/articles/846
到目前为止非常感谢。我更新我的问题。你介意看一下吗?
非常感谢您的详细回答。我可能需要一些时间才能完全理解 Ruby 类模型的概念。【参考方案2】:
如果您在 类 上定义方法,则可以在其 对象 上调用它。这是一个实例方法。
class Example
end
Example.send :define_method, :foo do
puts "foo"
end
Example.new.foo
#=> "foo"
如果您在 元类 上定义方法,则可以在 类 上调用它。这类似于类方法或其他语言中的静态方法的概念。
class Example
def self.metaclass
class << self
self
end
end
end
Example.metaclass.send :define_method, :bar do
puts "bar"
end
Example.bar
#=> "bar"
元类存在的原因是因为您可以在 Ruby 中做到这一点:
str = "hello"
class << str
def output
puts self
end
end
str.output
#=> "hello"
"hi".output
# NoMethodError
如您所见,我们定义了一个仅适用于一个字符串实例的方法。我们定义这个方法的东西叫做元类。在方法查找链中,首先访问元类,然后再搜索对象的类。
如果我们将String
类型的对象替换为Class
类型的对象,您可以想象为什么这意味着我们只在特定 类上定义一个方法,而不是在所有类上类。
当前上下文和self
之间的差异是微妙的,如果您有兴趣,可以read more。
【讨论】:
以上是关于Ruby 元类疯狂的主要内容,如果未能解决你的问题,请参考以下文章