单例类
单例类(又称元类或者本征类,请参阅下面的“单例类术语”解释)允许一个对象的行为能够区别于同一类的其他对象的行为。你以前可能已经见过打开单例类的一些语句了:
单例类术语
将元类这个术语用在单例类上似乎并不很准确。当称一个类为“元”的时候意味着它在某种意义上比普通的类更抽象。但也有例外,单例类仅仅是属于某特定实例的一个类。
真正的元类见于诸如提供了丰富的元对象协议的Smalltalk等语言中。Smalltalk的元类本身就是类而且其实例也是类。同样的,Ruby中唯一一个元类是Class,因为在Ruby中的所有类均是Class的实例。
另一个较普遍的用于替代单例类的术语叫做本征类,它源于德语eigen(意为“它自己的”)。一个对象的元类就是它的本征类(它自己的类)。
class A
end
objA = A.new
objB = A.new
objA.to_s # => "#<A:0x1cd0a0>"
objB.to_s # => "#<A:0x1c4e28>"
class >> objA #打开objA的单例类
def to_s; "Object A"; end
end
objA.to_s # => "Object A"
objB.to_s # => "#<A:0x1c4e28>"
class >> objA 表示打开objA的单例类。添加到单例类的实例方法与查找链路上的实例方法是一样的。其数据结构如图所示

上例中的objB是类A的一个实例。如果你问Ruby解释器,它将会告诉你objA也是类A的实例:
objA.class # => A
然而这仅仅是表象,其背后隐藏着其他的一些情况。另一个类对象已经被插入到查找链路中了。它就是objA的单例类。本文当中将其标识为“Class:objA”。Ruby也给他起了一个相似的名字:#<Class:#<A:0x1cd0a0>>。与所有的其它类一样,单例类的klass指针(未显示出来)也指向Class对象。
单例类在此被标记为虚类(flags中的一个标记位被用来标识其为一个虚类)。虚类不能被实例化,而且一般来说在Ruby中看不到它,除非我们特意去找它。当我们问Ruby objA的类是哪个的时候,它将跟随klass和super指针的继承路径上溯到第一个非虚类为止。
因此,Ruby将告诉我们objA的类是A。需要记住的一点是:一个对象的类可能与klass指向的对象并不一致。单例类之所以称其为单例是是因为每个对象仅有一个单例类。这样,我们才能够毫无歧义的找到“objA的单例类”或者Class:objA。在我们的代码中可以假设单例类确实存在;但事实上考虑到执行效率,它只有在第一次使用的时候才被创建。
Ruby允许单例类被创建在除了Fixnums和symbol的任意对象上。Fixnums和symbol是立即值(immediate values)(为了提高执行效率,它们本身将被直接存入到内存中,而不是用一个指针指向其数据结构)。因为它们自身被保存了进去,所以它们是没必要拥有klass指针的,进而也没有办法改变其方法查找路径。
你可以为true,false或者nil创建单例类,但是被返回的单例类与该对象原来的类一样。这些值分别是TrueClass,FalseClass及NilClass的单例实例(仅有的实例)。当你询问true的单例类是什么的时候,你将会得到TrueClass,因为立即值true是TrueClass唯一可能的实例。在Ruby中:
true.class # => TrueClass
class << true; self; end # => TrueClass
true.class == (class << true; self; end) # => true
类对象的单例类
这一节会比较复杂。要时刻记得方法查找的基本规则:首先Ruby根据对象的klass指针指向的对象进行方法查找;然后Ruby继续沿着查找链中的super指针的方向查找,直到找到合适的方法或者到达到最顶层。
需要牢记的另一重要事项就是类本身也是对象。就像传统的对象有一个单例类一样,类对象亦有其自己的单例类。这些单例类也可以象所有其他对象一样能够有各种各样的方法。既然一个类对象可以通过其klass指针来访问它的单例类,那么单例类的实例方法也就是这个类对象的类方法。下一段代码的完整数据结构如图所示:
class A
end

类A继承自Object。A的类对象就是Class类型。Class继承自Module而Module继承自Object。存储在A的m_tbl中的方法是A的实例方法。那么当在A上调用一个类方法将发生什么呢?
A.to_s # => "A"
将A作为接收者并以相同的方法查找规则进行方法的查找。(记住,A是一个代表A的类对象的常量)首先Ruby根据A指向Class的klass指针,在Class的m_tbl中查找名字为to_s的方法。没有查找到,Ruby继续跟随Class指向Module的super指针,在此处找到了to_s方法(在原始代码中为rb_mod_to_s)。
请不要感到惊奇,也没什么不可思议的。类方法可以被像实例方法相同的方式查找的到——唯一不同的是判断接收是类还是类的一个实例。
到现在为止我们已经知道类方法是如何被查找到的,这给了我们这样一种印象,那就是,似乎可以通过在Class对象(将它们插入到Class的m_tbl)上定义实例方法来达到在任何类上定义更多类方法。事实上,确实也是这样的:
class A; end
# from Module#to_s
A.to_s # => "A"
class Class
def to_s; "Class#to_s"; end
end
A.to_s # => "Class#to_s"
这是个非常有趣的小技巧,但实用程度很有限。通常我们希望在每个类中定义其独有的类方法。在这个时候类对象的单例类就要被用到了。为了开启一个类的单例类,只需要在定义单例类的语句中将类名当作对象传递给进去就可以了:
class A; end
class B; end
class <<A
def to_s; "Class A"; end
end
A.to_s # => "Class A"
B.to_s # => "B"
其结果数据结构如图所示。为了清晰起见,在这里类B被省略了。

to_s方法被加到A的单例类或者Class:A。现在,当调用A.to_s的时候,Ruby将跟随A指向Class:A的klass指针并且调用相适应的方法。
在方法定义中还有一点需要指出:在类或者模块的定义中,self总是指向类或模块对象:
class A
self # => A
end
因为在内部A与self均指向同一个对象,所以在A的类定义中,class << A 也可以被 写为class << self。这种惯用法在Rails的类方法定义中随处可见。下例将展示定义类方法的所有方式:
class A
def A.class_method_one; "Class method"; end
def self.class_method_two; "Also a class method"; end
class << A
def class_method_three; "Still a class method"; end
end
class << self
def class_method_four; "Yet another class method"; end
end
end
def A.class_method_five
"This works outside of the class definition"
end
class << A
def A.class_method_six
"You can open the metaclass outside of the class definition"
end
end
# Print the result of calling each method in turn
%w(one two three four five six).each do |number|
puts A.send(:"class_method_#{number}")
end
# >> Class method
# >> Also a class method
# >> Still a class method
# >> Yet another class method
# >> This works outside of the class definition
# >> You can open the metaclass outside of the class definition
这也意味着在一个单例类的定义中——就像在任何其他类的定义一样——self指向被定义的类对象。既然代码块或类定义的返回值是最后一条语句执行的返回值,那么class << objA; self; end 的值就是objA的单例类。代码class << objA将开启单例类,self(即单例类)就是从类定义中返回的。
将他们放在一起,我们能够开发Object类,可以添加一个实例方法到那些能够返回对象单例类的对象上:
class Object
def metaclass
class << self
self
end
end
end
此方法奠定了Metaid的基础,稍后将详述之。