为什么特征类看起来如此相似却不等价于自类呢?

我把备忘录忘在哪了,希望你能给我解释一下。

为什么物体的特征类不同于 self.class

class Foo
def initialize(symbol)
eigenclass = class << self
self
end
eigenclass.class_eval do
attr_accessor symbol
end
end
end

我将特征类与 class.self等同起来的逻辑思路相当简单:

class << self是声明类方法的一种方法,而不是实例方法。

因此,在对类对象的引用中,返回的 self应该与 self.class相同。这是因为为了定义类方法/属性,class << self会将 self设置为 Foo.class

我是不是有点糊涂了? 或者,这是 Ruby 元编程的诡计?

16713 次浏览

class << self is more than just a way of declaring class methods (though it can be used that way). Probably you've seen some usage like:

class Foo
class << self
def a
print "I could also have been defined as def Foo.a."
end
end
end

This works, and is equivalent to def Foo.a, but the way it works is a little subtle. The secret is that self, in that context, refers to the object Foo, whose class is a unique, anonymous subclass of Class. This subclass is called Foo's eigenclass. So def a creates a new method called a in Foo's eigenclass, accessible by the normal method call syntax: Foo.a.

Now let's look at a different example:

str = "abc"
other_str = "def"


class << str
def frob
return self + "d"
end
end


print str.frob # => "abcd"
print other_str.frob # => raises an exception, 'frob' is not defined on other_str

This example is the same as the last one, though it may be hard to tell at first. frob is defined, not on the String class, but on the eigenclass of str, a unique anonymous subclass of String. So str has a frob method, but instances of String in general do not. We could also have overridden methods of String (very useful in certain tricky testing scenarios).

Now we're equipped to understand your original example. Inside Foo's initialize method, self refers not to the class Foo, but to some particular instance of Foo. Its eigenclass is a subclass of Foo, but it is not Foo; it couldn't be, or else the trick we saw in the second example couldn't work. So to continue your example:

f1 = Foo.new(:weasels)
f2 = Foo.new(:monkeys)


f1.weasels = 4 # Fine
f2.monkeys = 5 # Also ok
print(f1.monkeys) # Doesn't work, f1 doesn't have a 'monkeys' method.

Hope this helps.

The simplest answer: the eigenclass can't be instantiated.

class F
def eigen
class << self
self
end
end
end
F.new.eigen.new #=> TypeError: can't create instance of virtual class

Yehuda Katz does a pretty good job of explaining the subtleties in "Metaprogramming in Ruby: It's All About the Self"