Ruby 中的單例

singleton_class 和Singleton Method

class 混入 module 時的相關思考

在Ruby裏, 咱們能夠經過在 class 裏混入 module 來達到一些用途,那麼 module 裏面的代碼是怎麼混入class 裏,又是被放置在了什麼地方呢?ruby

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 的方法:spa

  • 實例對象(obj) extend MyModule,code

  • 類E 的class << self 中 include MyModule對象

  • 類F extend MyModule.blog

從這個咱們能夠明白,Ruby能夠爲對象混入一個 module 來做爲拓展,那麼混入的代碼被放置到哪裏了呢?繼承

先說結論,本質上混入 module 都是將 Module 中的方法引入到對象(obj, 類E, 類F)的singleton_class(單件類)中.

在講單件類以前,先來看看單件方法.

Singleton Method (單件方法)

在 Ruby 裏string

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中類也是對象,而類名只是常量,因此在類上調用方法其實跟在對象上調用方法同樣:

類方法的實質是:它是一個類的單件方法,實際上若是比較單件方法的定義和類方法的定義,會發現其實兩者是同樣的.it

## 在定義單件方法時,存在着類似的地方
## 兩者均使用了def關鍵詞作定義。
## 上面的object能夠是*對象的引用、常量類名或者self。
def obj.a_singleton_method
end

def MyClass.another_class_method
end

因此,單件方法能夠認爲是添加在對象的某個空間內只針對該對象有效的方法.

若是對象是個實例對象,添加的方法就是他的單件方法,

若是對象是個類,添加的方法就是這個類的類方法.

 

singleton_class (單件類)

咱們知道Ruby中對象的方法的查找順序是:先向右,再向上,其含義就是先向右找到對象的類,先在類的實例方法中嘗試查找,若是沒有找到,再繼續順着祖先鏈找.

前面介紹的單件方法是指那些只針對某個對象有效的方法,那麼若是爲一個對象定義了單件方法,那麼這個單件方法的查找順序又應該是怎樣的?class

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.module

 


這是實例方法的查找規則

這是單件方法的查找規則

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',即 向右一步而後向上.

相關文章
相關標籤/搜索