ruby 中的单件方法和单件类

浏览: 122 发布日期: 2017-06-16 分类: ruby

singleton_class 和Singleton Method

class 混入 module 时的相关思考

在Ruby里, 我们可以通过在 class 里混入 module 来达到一些用途,那么 module 里面的代码是怎么混入class 里,又是被放置在了什么地方呢?

module MyModule  
  def a_method  
    puts "hello, MyModule#a_method"  
  end  
end 

## 直接extend module 
class F  
  extend MyModule  
end  
F.a_method ## hello, MyModule#a_method  
  
## class << self 中 include MyModule
class E  
  class << self  
    include MyModule  
  end  
end  
E.a_method ## hello, MyModule#a_method  

## 对象也是可以混入一个module
obj = Object.new  
obj.extend(MyModule)  
obj.a_method  ## hello, MyModule#a_method

上面的例子包含了三种引入 module 的方法:

  • 实例对象(obj) extend MyModule,

  • 类E 的class << self 中 include MyModule

  • 类F extend MyModule.

从这个我们可以明白,Ruby可以为对象混入一个 module 来作为拓展,那么混入的代码被放置到哪里了呢?

先说结论,本质上混入 module 都是将 Module 中的方法引入到对象(obj, 类E, 类F)的singleton_class(单件类)中.

在讲单件类之前,先来看看单件方法.

Singleton Method (单件方法)

在 Ruby 里

Ruby允许给单个对象增加方法,这种只针对单个对象生效的方法,称为单件方法。例如:

str = "I am a string"
def str.title?
  self.upcase == self
end
str.title? # => false
str.methods.grep(/title?/) # => [:title?]
str.singleton_methods  #=> [:title?]
str.class # => String
String.new.title? #=> NoMethodError

## 除此之外,还可以用 define_singleton_method 来定义单例方法
guy = "Bob"
guy.define_singleton_method(:hello) { "#{self}: Hello there!" }
guy.hello    #=>  "Bob: Hello there!"

Ruby中类也是对象,而类名只是常量,所以在类上调用方法其实跟在对象上调用方法一样:

类方法的实质是:它是一个类的单件方法,实际上如果比较单件方法的定义和类方法的定义,会发现其实二者是一样的.

## 在定义单件方法时,存在着相似的地方
## 二者均使用了def关键词做定义。
## 上面的object可以是*对象的引用、常量类名或者self。
def obj.a_singleton_method
end

def MyClass.another_class_method
end

所以,单件方法可以认为是添加在对象的某个空间内只针对该对象有效的方法.

如果对象是个实例对象,添加的方法就是他的单件方法,

如果对象是个类,添加的方法就是这个类的类方法.


singleton_class (单件类)

我们知道Ruby中对象的方法的查找顺序是:先向右,再向上,其含义就是先向右找到对象的类,先在类的实例方法中尝试查找,如果没有找到,再继续顺着祖先链找.

前面介绍的单件方法是指那些只针对某个对象有效的方法,那么如果为一个对象定义了单件方法,那么这个单件方法的查找顺序又应该是怎样的?

class MyClass
  def my_method
  end
end
  
obj = MyClass.new
  
def obj.my_singleton_method
end

首先,单件方法不会在obj中,因为obj不是一个类,其次它也不在MyClass中,那样的话所有的MyClass实例都应该能共享调用这个方法,也就构不成单件类了.同理,单件方法也不能在祖先链的某个位置(类似superclass: Object)中.

正确的位置是在单件类中,这个类其实就是我们在irb中向对象询问它的类时(obj.class)得到的那个类,不同的是这类与普通的类还是有稍稍不同的.也可以称其为元类或本征类.

前提及到的实例对象的单件方法和类的类方法在创建上是类似的,所以,通过的关键词class配合特殊的语法应该可以将其取到.class << obj.



这是实例方法的查找规则

这是单件方法的查找规则

class C  
  def a_method  
    puts "C#a_method"  
  end  
end  
  
class D < C  
    
end  
  
d = D.new  
d.a_method  

puts "abc".singleton_class  
  
class << d  
  def d_method  
    puts 'D#d_method'  
  end  
end  
puts d.singleton_class ## #<Class:#<D:0x007f9e88392d70>> 
puts d.singleton_class.class  ## Class
## 既然d.singleton_class 也是个类,那么久试着查一下他的父类
puts d.singleton_class.superclass ## D
## singleton_class是一个类,那么它肯定有父类,它的父类就是对象所属的类
## 单例方法则存在于对象的特征类中
puts d.singleton_class.instance_methods.grep(/d_method/)  ## d_method

打开单件类

Ruby 中 class 作用是把代码的上下文变换到这个类中,也就是『打开类』,同一个类可以在任何地方被打开,也因此别人的类可以被自己随意打开并改写.

Ruby提供了两种方法获取单件类的引用,一种是通过传统的关键词class配合特殊的语法。

通过关键字 class << obj 打开实例对象的单件类空间,为对象添加一系列的单件方法.单件类的本质还是一个类,是一个拥有唯一实例的类,所以叫单件类,或者单例类.

class << an_object
  # 自己的代码,定义对象的一系列单件方法
end
  
obj = Object.new
singleton_class = class << obj
  self
end
singleton_class.class # => Class


另一个方法是,通过Object#singleton_class方法来获得单件类的引用:

singleton_class = 'abc'.singleton_class  # => #<Class: #<String:0xxxxxx>>
singleton_class.class # => Class

单件类的特性

1. 每个单件类只有一个实例(被称为单件类的原因),而且不能被继承.

2. 单件类是一个对象的单件方法的存活所在.

3. 引入单件类后的方法查找

基于上面对单件类的基本认识,引入单件类后,Ruby的方法查找方式就不应该是先从其类(普通类)开始,而是应该先从对象的单件类中开始查找,如果在单件类中没有找到想要的方法,它才会开始沿着类(普通类)开始,再到祖先链上去找.这样从单件类之后开始,一切又回到了我们在没有引入单件类时候的次序.

类方法,实例方法,单例方法

类方法只有类本身可以调用,在ruby中,类方法是一种特殊的单例方法.之前的例子中可以得到这样的结论,singleton_class也是一种类,在ruby中所有的类又都是对象.对象都有对应的singleton_class.

Ruby 中方法的查找规则
注释: 以#号标示的是singleton_class, c标示 (eigen)class, s表示 superclass

class C  
  def a_method  
    puts "C#a_method"  
  end  
  
  def self.a_class_method  
    puts "C#a_class_method"  
  end  
end  
  
class D < C  
end  

obj = D.new

class << obj  
  def a_singleton_method  
    puts 'obj#a_singleton_method'  
  end  
end  

# 欲调用 obj.a_singleton_method
# 此时调用对象是obj ,则会先去 #obj 处查找(向右一步)
# #obj
puts obj.a_singleton_method  ## obj#a_singleton_method

# 欲调用 D.a_class_method
# 此时调用对象是D ,则会先去 #D 出查找(向右一步)
# D.singleton_class 然后往上查(superclass)
puts D.singleton_class.superclass  ## #C
# #D -> #C
# D的类方法方法 a_class_method 在 #C 里
puts D.a_class_method ## C#a_class_method 

# 欲调用 obj.a_method
# 此时调用对象是obj ,则会先去 #obj 处查找(向右一步)
puts obj.singleton_class ## #<Class:#<D:0x007f9e88340b60>>
# obj.singleton_class 然后往上查(superclass)
puts obj.singleton_class.superclass ## D
# #obj 的父类 是 D 然后往上查(superclass)
puts D.superclass  ## C
# D 的父类 是 C
# 查找顺序即: #obj -> D -> C
# D的实例方法 a_method 在 类C 里
obj.a_method  ## C#a_method

Ruby 中方法的查找 遵循的规则是 'one step to the right, then up',即 向右一步然后向上.

返回顶部