Ruby 對象模型的復(fù)習(xí)

最近復(fù)習(xí)了下 ruby 對象模型的知識,參照了 Ruby Metaprogramming昏兆,于是邊看邊做筆記,還是收獲很多。

Open Class

class String
  def my_method
    "#{self} length is #{self.length}"
  end
end

class 更像是一個作用于操作符意系,而不是聲明語句。

創(chuàng)建類是一個令人愉快的副作用饺汹。

class 關(guān)鍵字的核心任務(wù)式把你帶到類的上下文中蛔添,讓你可以在里面定義方法。使用它可以打開類兜辞,進(jìn)行動態(tài)修改迎瞧。

Inside the Object Model

對象包含實例變量,可以使用 Object#instance_variables 查看

類的名字是一個常量

對象的類和實例變量并沒有關(guān)系逸吵,給它賦值的時候凶硅,它們就出現(xiàn)了。每個對象的實例變量不同扫皱。

解釋器中足绅,一個對象僅僅包含它自己的實例變量,以及一個對自身 Class 的引用啸罢。

為了共享编检,方法必須存放在類中,而非對象中扰才。

String.instance_methods == 'aaa'.methods # true
String.methods == String.instance_methods # false

類本身就是對象,是 Class 類的實例厕怜。

Class.instance_methods(false) # [:allocate, :new, :superclass]

如果你希望代碼被 include 進(jìn)去衩匣,就用模塊,如果你希望某段代碼被實例化或者繼承粥航,就應(yīng)該使用類琅捏。

[].class.class # Class
[].superclass # Object

Constant and path

幫助我們找到常量、類等递雀。:: 表示 root-level柄延。

module M1
  class C1
    X = 'constant'
  end
  C1::X
end

M1::C1::X

:: # root-level

Y = 'root-level constant'

module M
  Y = 'constant in M'
  Y
  ::Y
end

Module.nesting 表示當(dāng)前路徑。

Objects and Classes 小結(jié)

什么是對象缀程,對象就是一組實例變量和一組指向其類的引用搜吧。

對象的方法并不存在與對象本身,而是存在與對象的類中杨凑。在類中滤奈,稱為實例方法。

類是一個對象 ( Class 類的一個實例)外加一組實例方法和一個對超類(Superclass)的引用撩满。Class 類是 Module 類的子類蜒程,所以類也是一個模塊绅你,但是你不能 include,也不能 prepend 類昭躺。

以下一個很著名的 pattern忌锯,用來實現(xiàn)類,可以思考這樣做的目的是什么领炫。

module Hello
  module ClassMethods
    def class_m
      'ClassMethods'
    end
  end
  
  module InstanceMethods
    def instance_m
      'InstanceMethods'
    end
  end
  
  def self.included(receiver)
    receiver.extend         ClassMethods
    receiver.send :include, InstanceMethods
  end
end

class C
  include Hello
end

Method lookup

ruby 進(jìn)行方法查找遵循向右偶垮,向上的原則。

receiver 是調(diào)用方法的對象驹吮,明確方法的 receiver 是非常重要的针史。

ancestors 是祖先鏈,表示繼承關(guān)系碟狞。ruby 先在接受者中查找方法啄枕,再沿著祖先鏈向上查找,直到找到為止族沃。

module M1, Class C,

C include M1

Class D < Class C

D.ancestors # [C, M1, Object, Kernel, BaseObject]

M1 M2 誰 include 在前频祝,就在 ancestors 的前面。

C include M1
C include M2
C.ancestors # [C, M2, M1, Object, Kernel, BaseObject]

而 prepend 將會導(dǎo)致如下:

C prepend M1
C.ancestors # [M1, C, Object, Kernel, BaseObject]

如果一個類或者模塊已經(jīng)出現(xiàn)在祖先鏈中脆淹,將會忽略 prepend 與 include常空。

Kernel Module 提供內(nèi)核方法,被 Object include盖溺。你當(dāng)然可以用它做點“壞事”漓糙。

Self

Ruby 中每一行代碼都會在一個對象中被執(zhí)行,當(dāng)前對象用 self 表示烘嘱,也可以用 self 對其進(jìn)行訪問昆禽。

private 關(guān)鍵字只能被隱形的 self 調(diào)用。

私有規(guī)則:如果接受者不是自己蝇庭,就必須指明接受者醉鳖;私有方法只能被隱性的接受者調(diào)用;

私有方法可以被繼承哮内。

# main - top level context

irb -> self # => main
self.class # => Object

一個私有規(guī)則的例子:

class Klass
  def method_a
    method_b
  end

  private

  def method_b
    'hi'
  end
end

Klass.new.method_a

class Klass
  def method_a
    self.method_b
  end
end

Klass.new.method_a

# NoMethodError: private method `method_b' called for #<Klass:0x007fd0d50ad428>

因為不滿足私有規(guī)則盗棵,所以丟出 NoMethodError 的錯誤。

在上面這個例子中北发,如果不使用 self.class 的話纹因,會發(fā)生什么后果?

class Point
  attr_reader :x, :y

  def initialize(x, y)
    @x, @y = x, y
  end

  def +(other)
    self.class.new(@x + other.x, @y + other.y)
  end

  def -(other)
    self.class.new(@x - other.x, @y - other.y)
  end

  def to_s
    "{#{@x}, #{@y}}"
  end
end

Refine

Refine 是用來代替猴子補丁的一種做法鲫竞,也不能完全替代猴子補丁辐怕。

module StringExtensions
  refine String do
    def my_length
      "#{self} length is #{self.length}"
    end
  end
end

module StringStuff
  using StringExtensions  
  'foo'.my_length
end

在兩種情況下會起到作用:

  1. refine block 內(nèi)部
  2. using 開始到模塊結(jié)束,或者文件結(jié)束

這里有個很有意思的例子說明 refine从绘,可以猜猜輸出是什么寄疏。

class MyClass
  def my_method
    'original'
  end

  def another_method
    my_method
  end
end

module MyClassRefine
  refine MyClass do
    def my_method
      'refined my_method'
    end
  end
end

module Run
  using MyClassRefine
  p MyClass.new.my_method # ???
  p MyClass.new.another_method # ???
end

Dynamic Methods & Dispatch

Object#send Object#public_send 幫助我們實現(xiàn)了 dynamic dispatch是牢,是一種很好用的反射方式。

Module#define_method 幫助我們動態(tài)的創(chuàng)建方法陕截。書上最后的例子是非常值得學(xué)習(xí)的驳棱,請牢記我的 comments。

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
    data_source.methods.grep(/^get_(.*)_info/) do # 使用內(nèi)省的方式自動創(chuàng)建农曲,進(jìn)行部分解耦
      self.class.define_component $1 # 使用 self.class 避免子類丟失方法
    end
  end

  def self.define_component(name)
    define_method(name) do
      # dynamics dispatcher # 動態(tài)的調(diào)用
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.capitalize}: #{info} (#{price})"
      price >= 100 ? "* #{result}" : result
    end
  end
end

Ghost Methods

BacisObject#method_missing 是當(dāng)沒有按照向右社搅,向上查找后調(diào)用,參數(shù)為 (method, *args, &block)乳规,Ruby 很靈活形葬,這個表示如果沒有找到你的方法,那就調(diào)用這個暮的,所以可以 override 這個家伙幫我們制作那些并不存在于 Object#methods 的方法件甥。

BacisObject#method_missing 也可以用其來實現(xiàn)動態(tài)的方法調(diào)用盯腌,注意不是定義凌埂。

所以 ghost method 并不是真正的方法触幼,我們知道當(dāng)你通過 Object#response_to? 查詢一個方法時,將會返回 true恨闪,而對于 ghost method倘感,將會返回 false。而 response_to 將調(diào)用 response_to_missing咙咽,所以千萬不要忘記要重現(xiàn) response_to_missing老玛。

我非常喜歡下面的例子,可以想想如果沒有 number = 0 將會發(fā)生什么钧敞。Ghost Methods 十分強大逻炊,所以一定要小心,don't break anything犁享。

 def method_missing(name, * args)
    person = name.to_s.capitalize
    super unless is_our_member? person
    number = 0
    3.times do
      number = rand(10) + 1
      puts "#{number} ..."
    end
    "#{person} got a #{number}"
  end

Ghost Methods 產(chǎn)生風(fēng)險的原因是他們并非真正的方法,只是對方法的攔截豹休。你有很多規(guī)避放的地方炊昆,比如必須要調(diào)用 super;還需要更新 responsd_to_missing?威根。使用時凤巨,牢記那個有意思的 bug。

Dynamic Methods 就是真正的方法洛搀,只是定義的方式不一樣罷了敢茁。

某時,你只能使用 Ghost Methods留美,例如 JSON 庫彰檬,你無法確定有多少種標(biāo)簽?zāi)阈枰С稚烊小τ?DAO 的話,根據(jù) table schema 可以簡單的使用 define_method 生成你想要的方法逢倍。

在可以使用動態(tài)方法的時候捧颅,使用;除非必須使用 Ghost Methods较雕,否則盡量不使用碉哑。

Blocks Are Closures

代碼塊即包含了代碼,也包含了一組綁定亮蒋。代碼庫在定義時扣典,獲得定義中的綁定,運行時慎玖,帶著綁定進(jìn)入該方法贮尖。

def my_method
  x = 'good bye'
  yield('cruel')
end

x = 'hello'
my_method {|y| "#{x},#{y} world" }

代碼塊獲取局部綁定,一直攜帶著這些綁定凄吏。在 JavaScript 中远舅,我們往往這樣描述閉包:閉包是一個擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個函數(shù)),因而這些變量也是該表達(dá)式的一部分痕钢。牢記執(zhí)行的代碼以及攜帶的綁定可以幫你解決很多問題图柏。

提到閉包,就不能不提到作用域 Scope任连。只要程序切換了 Scope蚤吹,有些綁定就會被新的綁定所取代。

程序會在三個地方關(guān)閉前一個作用域随抠,同時打開一個新的作用域:類定義裁着,模塊定義,方法拱她。

在 class/module 與 def 之間還有一個小區(qū)別二驰,在類定義與模塊定義中的代碼會立刻執(zhí)行(這是很多 Java 程序員很難理解的地方),例如:

class B
  def self.some
    'bar'
  end

  some #=> bar
end

下面使用 Class.new 中的 block 穿越作用域秉沼,來達(dá)到扁平作用域的作用桶雀,使用閉包穿越作用域 ,在 Class.new 中唬复,調(diào)用到另一個作用域里的變量矗积。可以試試如果使用 class 關(guān)鍵字敞咧,該怎么辦棘捣。

my_var = "Success"

MyClass = Class.new do
  puts "#{my_var} in class"

  define_method :my_method do
    puts "#{my_var} in my_method"
  end
end

MyClass.new.my_method

Proc & Lambda

你可以把 block 傳遞給 Proc.new 方法來創(chuàng)建一個 Proc 對象,然后使用 Proc#call 來執(zhí)行休建,技巧叫做 Deferred Evaluation乍恐,也叫做 Lazy Loading评疗。如果你對函數(shù)式編程比較熟悉的話,這個概念應(yīng)該不陌生禁熏。

有兩個內(nèi)核方法可以把 block 轉(zhuǎn)換為 Proc:proc & lambda壤巷。

p = lambda { |x| x + 1 }
p = -> (x) { x + 1} # 簡寫

& 操作符的意思是,這是一個 block 對象瞧毙,我想把它當(dāng)做 block 來使用胧华,去掉 &,就會得到一個 block 對象宙彪,例如:

class C
  def foo
    'foo'
  end

  def bar(&block)
    block.call
  end
end

c = C.new
m = c.method :foo
c.bar m # error!
c.bar &m # to block
c.bar do
  'hi'
end # hi

proc 和 lambda 的區(qū)別:

區(qū)別1: return 是從定義 proc 的作用域中返回矩动, lambda 則表示在 lambda 中返回。
區(qū)別2: lambda 的參數(shù)數(shù)量是嚴(yán)格的释漆,更像是方法悲没。

def method_a(callable)
  callable.call * 2
end

p = proc { return 10 }
method_a p # error

p = proc { 10 }
method_a p

p = lambda { return 10 }
method_a p

Method 對象

可以使用 Object#method 方式獲取 method 對象。

method 對象是可以被執(zhí)行的男图,Method#call 將會執(zhí)行示姿,參看 Duck Typing。method 對象可以通過 Method#to_proc 轉(zhuǎn)為 Proc逊笆。

proc栈戳,lambda 是在定義的作用域中執(zhí)行,而 method 對象是在自身所在對象的作用域中執(zhí)行难裆。

class Foo
  def bar(&callable)
    callable.call
  end

  def some_method
    x
  end

  def x
    'xxx in Foo'
  end
end

x = 'xxx'
m = proc do
  x
end

f = Foo.new

f.bar &m
f.bar &(f.method :some_method)

Class Definition Demystified

我們可以在類定義中放入任何代碼子檀,最后一條語句就是返回值。

在定義(模塊)時乃戈,類本身就是當(dāng)前對象的 self褂痰,類和模塊也是對象,所以類是可以充當(dāng) self 的症虑。self 是當(dāng)前的對象缩歪。

  • 在程序頂層,當(dāng)前類是 Object谍憔,是 main 對象所屬的類
  • 在方法中驶冒,當(dāng)前類就是當(dāng)前對象的類。
  • 使用 class 或者 module 打開時韵卤,所打開的類是當(dāng)前類。
class C
  def m1
    def m2
    end
  end
end

class D < C
end

c.instance_methods false # => :m1 在此時崇猫,并沒有執(zhí)行 m1, 則只有 m1
D.new.m1 # => 執(zhí)行了 m1, 但是 m2 定義在了 C 上
c.instance_methods false # => :m1, :m2

另一個例子:

def add_method(klass)
  puts self #  => main
  klass.class_eval do
    puts self # => String or other class
    def m
      puts self # => instance
      'hello'
    end
  end
end

add_method String
  • 所有使用 def 定義的方法都是當(dāng)前類的實例方法
  • 在類的定義中沈条,當(dāng)前類就是 self —— 正在定義的類
  • 如果有引用,則可以使用 class_eval 打開這個類诅炉,self 則會被修改

Ruby 解釋器假定所有的實例變量都屬于當(dāng)前對象 self蜡歹,所以在類定義中屋厘,我們是可以使用 @ 的。@x 為類實例變量 Class Instance Variables月而。只能被自身訪問汗洒,不能被類的實例或者子類所訪問。

class MyClass
  @x = 1
  def self.read
    @x
  end
end

MyClass.read

Singleton Methods & Objects

我們之前提到父款,可以單獨的為一個對象增加獨有的方法溢谤,例如:

str = 'sss'

def str.foo
   "bar #{self}"
end

現(xiàn)在問題來了,這個方法在哪里憨攒?按照之前提到的方法查找世杀,我們應(yīng)該去查找的是 String#foo ,但是其中并沒有定義這個方法肝集。答案是瞻坝,這個方法會被放在了 單例類 中。

那么以前我們提到的類方法是什么呢杏瞻?也許你已經(jīng)明白了所刀,類是一個對象,是 Class 的對象捞挥。所以類方法的本質(zhì)是:它就是一個類(class 對象)的單件方法浮创。


class C
  def a_method
    'C#a_method'
  end
end

class D < C

end

obj = D.new

p obj.a_method # => C#a_method

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

p obj.a_singleton_method # => "obj#a_signleton_method"
p obj.singleton_class # => #<Class:#<D:0x007fe5c605ec50>>
p obj.singleton_class.superclass #=> D

class C
  class << self
    def a_class_method
      'C.a_class_method'
    end
  end
end

p C.a_class_method # => 'C.a_class_method' 
p D.a_class_method # => 'C.a_class_method'

p C.singleton_class # => #C
p D.singleton_class # => #D

p D.singleton_class.superclass # => #C
p C.singleton_class.superclass # => #Object

所以我們修改了方法查找:

Object.singleton_class => #Object
Object.singleton_class.superclass => #BaseObject
Object.singleton_class.superclass.superclass.class => Class

歸根結(jié)底,我們補充了 Ruby 的對象模型树肃,并且給出了最重要的 7 條規(guī)則:

  1. 只有一種對象——要么是普通對象蒸矛,要么是模塊或者類
  2. 只有一種模塊——普通模塊、一個類或者單件類
  3. 只有一種方法胸嘴,存在于模塊中——通常在類中
  4. 每個對象都有自己的“真正的類”——要么是一個普通類雏掠,要么是一個單件類
  5. 除了 BaseObject 沒有超類,每個類都有且只有一個祖先
  6. 一個對象的單件類的超類是這個對象的類劣像,一個類的單件類的超類是這個類的超類的單件類
  7. 調(diào)用一個方法乡话,Ruby 先向右去找接收者真正的類,再向上查找
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耳奕,一起剝皮案震驚了整個濱河市绑青,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屋群,老刑警劉巖闸婴,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芍躏,居然都是意外死亡邪乍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庇楞,“玉大人榜配,你說我怎么就攤上這事÷郎危” “怎么了蛋褥?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長睛驳。 經(jīng)常有香客問我烙心,道長,這世上最難降的妖魔是什么柏靶? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任弃理,我火速辦了婚禮,結(jié)果婚禮上屎蜓,老公的妹妹穿的比我還像新娘痘昌。我一直安慰自己,他們只是感情好炬转,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布辆苔。 她就那樣靜靜地躺著,像睡著了一般扼劈。 火紅的嫁衣襯著肌膚如雪驻啤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天荐吵,我揣著相機與錄音骑冗,去河邊找鬼。 笑死先煎,一個胖子當(dāng)著我的面吹牛贼涩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播薯蝎,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼遥倦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了占锯?” 一聲冷哼從身側(cè)響起袒哥,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎消略,沒想到半個月后堡称,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡艺演,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年粮呢,在試婚紗的時候發(fā)現(xiàn)自己被綠了婿失。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡啄寡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哩照,到底是詐尸還是另有隱情挺物,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布飘弧,位于F島的核電站识藤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏次伶。R本人自食惡果不足惜痴昧,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冠王。 院中可真熱鬧赶撰,春花似錦、人聲如沸柱彻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哟楷。三九已至瘤载,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卖擅,已是汗流浹背鸣奔。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惩阶,地道東北人挎狸。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像琳猫,于是被迫代替她去往敵國和親伟叛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理脐嫂,服務(wù)發(fā)現(xiàn)统刮,斷路器,智...
    卡卡羅2017閱讀 134,637評論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉账千,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言侥蒙,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,186評論 0 7
  • Objective-C語言是一門動態(tài)語言匀奏,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運行時來處理鞭衩。這種動態(tài)語言...
    tigger丨閱讀 1,390評論 0 8
  • 搖滾的 時間 ? 流落在 桌邊 靜 動 態(tài)勢 歡 歌 升 平 燈 紅 酒 綠 舞 姿 妖 嬈 蜷 縮 街 角 看 ...
    東方清羽閱讀 124評論 0 2