由于所有方法都是通过类定义实现和存储的,因此对象定义其自己的方法应该是不可能的。然而,Ruby提供了一种解决方案-您可以定义仅适用于特定对象的方法。这些方法称为Singleton方法。
class Instance
def instance_method
puts "instance method called"
end
end
in_obj = Instance.new
上面的语句给出了一个Instance类的参考对象,你可能会得到一个不同的id。
#<Instance:0x00000000e9ee68>
让我们使用对象调用实例方法并获取输出结果。
in_obj.instance_method
instance method called
现在,创建单例方法。
sin_obj = Instance.new
#<Instance:0x00000000e98d60>
def sin_obj.singleton_method
puts "singleton method called"
end
调用单例方法
sin_obj.sigleton_method
singleton method called
在此,对象sin_obj具有单例方法singleton_method。这是一个单例方法,因为它属于类的一个实例,并且不会与其他实例共享。
然而,单例方法与实例对象不能持有方法的观点相矛盾,只有类定义(类Class的对象)可以。事实上介于两者之间。当您在对象上声明单例方法时,Ruby会自动创建一个仅容纳该方法的类。这个类被称为对象的“元类”。此后该对象的所有单例方法都归于其元类。每当您向该对象发送消息时,它首先查看其元类是否存在该方法。如果不存在,则将其传播到对象的实际类中,如果也未找到,则消息遍历继承层次结构。让我们在同一Instance类中定义类方法。现在,让我们用一个例子来检查我上面说的事实。
foo = "Hey I am a string"
def foo.foo_instance_method
p self.upcase
end
验证单例模式定义是否进入元类。
p foo.class.instance_methods.include?:foo_instance_method
false
p foo.metaclass.instance_methods.include?:foo_instance_method
true
p foo.metaclass.class
Class
p foo.metaclass
#<Class:#<String:0x00000000e8f0f8>>
让我们来看一下元类是如何实现的。
class Instance
def metaclass
class << self
self
end
end
end
类 << self 语法将当前的self更改为指向当前对象的元类。由于我们已经在实例方法内部,这将是实例的元类。该方法只是简单的返回此时的self,它是实例的元类。
元类在许多方面几乎像一个类。但是它不能被实例化。请参见下面的示例以进行检查。
a = Instance.new
a.metaclass.new
You will get is error when try to intantiate the meta class.
*TypeError: can't create instance of singleton class*
在Ruby中,“metaclass”和“singleton class”是相同的。在Ruby文献中,你会发现它们互换使用。
class Instance
def self.class_method
puts "class method called"
end
end
然而,也可以按照这种语法来定义一个类方法。
class Instance
class << self
def show
puts "Class method show invoked"
end
end
end
调用 class_method 方法
Instance.class_method
class method called
Instance.show
Class method show invoked
它由为当前对象(self)构建一个新的匿名类组成。要创建匿名类,我们使用<<符号。仔细查看类方法和单例方法的定义,你会发现它们之间的区别。
考虑以下示例。
class Instance
puts self
def meth
p self
end
end
在这里,meth方法外的self只是类,而在instance_method中,self指向当前实例或对象。
此外,您还可以检查方法是否响应特定对象。
p Instance.new.respond_to?(:singleton_method)
p Instance.new.respond_to?(:class_method)