這是 Metaprogramming Ruby 2 的閱讀筆記,只會記錄我覺得重要的地方。如果你想要了解完整的內容或是想讓Ruby程式做一些神奇的事,強烈推薦去讀讀這本書。
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可以用。
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。
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之間的關係可以從下圖了解:
@var是a的一個instance variable,instance variable是在instance a中定義的,跟A一點關係也沒有,也就是即使是同一個class的instance,也可以有完全不同的instance variable。另外從上面的例子可以知道@var是在say被呼叫之後才被定義,也就是instance variable是可以動態被建立的,而不是預先定義。
say是A的一個instance method,instance method是定義在A中,但只有A的instance a才可以呼叫。你會發現a.methods與A.instance_methods列出來的結果是一樣的(在沒有include其它module的情況下),也就是instance method是定義在A中,而每一個instance都會共用相同的instance methods。
Class繼承Module表示每一個Class都是一個Module,不過在使用上我們會做區分,module表示會被include在其它的地方,而class會用來做instance或是建立繼承體系。
在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]
MyClass
。::A
。Module.constant
會列出所有root-level的constants。 Module.nesting
則可以列出目前的namespace。請參考: ruby的load與require是差在什麼地方?
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它所對應的繼承體系。
請參考: 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]
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這個有趣的例子。
ruby的每一行程式碼都是在某個物件中執行,我們稱做current object,而ruby會把current object記錄到 self
之中。如果呼叫一個method但不指定receiver的話,預設就會把self當做receiver。當你呼叫method時指定了receiver,則self會在呼叫時method切換到對應的receiver。
在ruby中,private method指的是不能使用receiver呼叫的method,即使是self也不行。這樣的定義表示除了在物件本身之中呼叫外,其它的地方都無法呼叫這個method。
在一開始進入irb或是ruby的程式而尚未呼叫任何的method時,這時候的self會是一個叫做 main
的Object,我們稱做top-level context,它是在程式一開始時由ruby interpreter自動配置的物件。(雖然叫做main,可是與C或是Java裡的main()義意完全不同。)
在class中,self會指向class本身。
class A
self # => A
end
請參考: ruby的refinement