Metaprogramming Ruby 2 - ch2 - The Object Model
2016-04-10 13:43:13

前言

這是 Metaprogramming Ruby 2 的閱讀筆記,只會記錄我覺得重要的地方。如果你想要了解完整的內容或是想讓Ruby程式做一些神奇的事,強烈推薦去讀讀這本書。

Open Classes

Open Class:我們可以在任何時間點reopen已經存在的class並修改它。

class A
  def say
    "Hi"
  end
end

a = A.new
p a.say # Hi
# p a.say_hello # NoMethodError

class A
  def say_hello
    "Hello"
  end
end

p a.say # Hi
p a.say_hello # Hello

上面的例子,第二個class A並不是重新定義class A,而是reopen class A,並加一個新的method在class A之中。有趣的是即使是在reopen之前宣告的instance a,在reopen加上method之後就會有新的method可以用。

The Problem with Open Classes

class A
  def say
    "Hi"
  end
end

a = A.new
p a.say # Hi

class A
  def say
    "Hello"
  end
end

p a.say # Hello

由上面的例子可以發現reopen之後我們可以將原本存在在class的method履寫掉,這樣的特性會在使用open class時很容易不小心改掉class原本的method而造成非預期的bug,所以這個技巧又被稱做Monkeypatch

Inside the Object Model

class A
  def say
    @var = 1
    "Hi"
  end
end

a = A.new

p a.class # A
p a.methods # [:say, ... ]
p a.instance_variable # []

p a.say # Hi

p a.instance_variable # [:@var]

p A.class # Class
p A.instance_methods # [:say, ... ]
p A.superclass # Object

p Class.class # Class
p Class.superclass # Module
p Class.instance_methods # [:new, ... ]

p Module.superclass # Object

p Object.superclass # BasicObject

p BasicObject.superclass # nil

a, A, Class, Module, Object, BasicObject之間的關係可以從下圖了解:

ruby 物件關係圖
ruby 物件關係圖

Instance Variables

@var是a的一個instance variable,instance variable是在instance a中定義的,跟A一點關係也沒有,也就是即使是同一個class的instance,也可以有完全不同的instance variable。另外從上面的例子可以知道@var是在say被呼叫之後才被定義,也就是instance variable是可以動態被建立的,而不是預先定義。

Methods

say是A的一個instance method,instance method是定義在A中,但只有A的instance a才可以呼叫。你會發現a.methods與A.instance_methods列出來的結果是一樣的(在沒有include其它module的情況下),也就是instance method是定義在A中,而每一個instance都會共用相同的instance methods。

Modules

Class繼承Module表示每一個Class都是一個Module,不過在使用上我們會做區分,module表示會被include在其它的地方,而class會用來做instance或是建立繼承體系。

Constants

在ruby中會把class或是module的命名稱做constants,它們都會是大寫字母開頭。Constants在ruby中是可以被變更的(書上舉Rake的例子還蠻有趣的),與變數不同的是它有namespace的概念。

A = "A root-level constant"

module B
  class C
    A = "A constant in a class"

    def self.get_class_a
      A
    end

    def self.get_root_a
      ::A
    end

    def self.show_nesting
      Module.nesting
    end
  end

  def self.show_nesting
    Module.nesting
  end
end

p A # "A root-level constant"
p B::C::A # "A constant in a class"

p B::C.get_class_a # "A constant in a class"
p B::C.get_root_a # "A root-level constant"

p B.constants # [:C]
p B::C.constants # [:A]
p Module.constants # [:A, :B, :Object, :Module, :Class, :BasicObject, :Kernel, ... ]

p B.show_nesting # [B]
p B::C.show_nesting # [B::C, B]
  • class或是module的命名我們會使用pascal cased,例如MyClass
  • 可以使用::來取得root的constants,例如上面的 ::A
  • Module.constant會列出所有root-level的constants。 Module.nesting則可以列出目前的namespace。

Loading and Requiring

請參考: ruby的load與require是差在什麼地方?

What Happens When You Call a Method?

Method Lookup

class A
  def say
    "Hi"
  end
end

class B < A
end

b = B.new
p b.say # Hi
B.ancestors # [B, A, Object, Kernel, BasicObject]

instance b呼叫了say,這時候ruby會先去找對應這個instance的class,也就是B有沒有定義這個method,如果沒有則往繼承體系上找有沒有這個method,以上面的例子而言,在class A中找到了say,於是就會使用class A中定義的say。我們可以使用 ancestors來查看一個class它所對應的繼承體系。

Modules and Lookup

請參考: ruby module的用法

class A
  def say
    "A say Hi"
  end
end

module M1
  def say
    "M1 say Hi"
  end
end

module M2
  def say
    "M2 say Hi"
  end
end

class B < A
  include M1
  prepend M2
  def say
    "B say Hi"
  end
end

class B
  def say
    "Reopen B say Hi"
  end
end

module M3
  def say
    "M3 say Hi"
  end
end

module M4
  def say
    "M4 say Hi"
  end
end

module SayRefinement
  refine B do
    include M3
    prepend M4
    def say
      "Refinement B say Hi"
    end
  end
end

b = B.new
p b.say # "M2 say Hi"
p B.ancestors # [M2, B, M1, A, Object, Kernel, BasicObject]

using SayRefinement

p b.say # "M4 say Hi"
p B.ancestors # [M2, B, M1, A, Object, Kernel, BasicObject]
Method Lookup的順序
Method Lookup的順序

The Kernel

Kernel Method:因為Object有include Kernel module,因此ruby中所有的物件都可以使用Kernel內定義的method。換句話說,只要將method加入Kernel中,則所有的物件都會擁有這個method,這會讓method變成像是ruby keyword的錯覺,酷。

module Kernel
  def say
    "Hi"
  end
end

p say

class A
end

a = A.new
p a.say

上面的例子,我們用Open Class的方式將say塞到Kernel中,這時候say變成到處都可以用了。書上也舉了AwesomePrint這個有趣的例子。

Method Execution

The self Keyword

ruby的每一行程式碼都是在某個物件中執行,我們稱做current object,而ruby會把current object記錄到 self之中。如果呼叫一個method但不指定receiver的話,預設就會把self當做receiver。當你呼叫method時指定了receiver,則self會在呼叫時method切換到對應的receiver。

Private Method

在ruby中,private method指的是不能使用receiver呼叫的method,即使是self也不行。這樣的定義表示除了在物件本身之中呼叫外,其它的地方都無法呼叫這個method。

The Top Level

在一開始進入irb或是ruby的程式而尚未呼叫任何的method時,這時候的self會是一個叫做 main的Object,我們稱做top-level context,它是在程式一開始時由ruby interpreter自動配置的物件。(雖然叫做main,可是與C或是Java裡的main()義意完全不同。)

Class Definitions and self

在class中,self會指向class本身。

class A
  self # => A
end

Refinements

請參考: ruby的refinement