Chapter 2: The Object Model - Part I
Open Class 開放類別
Open class 開放類別是 Ruby 程式語言的特色之一,所謂的開放類別就是讓 Ruby 內部的類別可以增加,甚至是改寫原有的方法。以下面的範例來說:
class Dog
def jump
'jump'
end
end
class Dog
def run
'run'
end
end
dog = Dog.new
dog.jump # => "jump"
dob.run # => "run"
當 Ruby 第一次遇到 class Dog 時,發現 class Dog 並不存在,所以會定義此類別及 jump()方法。接者第二次再遇到 class Dog 時,會知道 class Dog 已經存在於 Ruby 了,因此會 “打開” 類別,讓 run()方法可以進入而類別 Dog 就會多了 run() 方法了。
當然你也可以改寫 Ruby 內建類別,例如:String 、Array 等…
grade = [90, 88, 4]
grade.size # => 3, 還未改寫內部size方法前
class Array
def size
10 # => 只要是呼叫size方法就會回傳10
end
end
grade.size # => 10, 改寫size方法後
The Problem with Open Classes
” The term monkey patch only refers to dynamic modifications of a class or module at runtime, motivated by the intent to patch existing third-party code as a workaround to a bug or feature which does not act as desired. “ ― Wikipedia ―
Monkey Patching
開放類別常見的問題就是在複雜的專案中,如果沒有適當的控管,有可能會修改到相同名字方法,而造成與之前撰寫好的程式碼產生衝突。當我們只是為了要解決程式裡的某個 bug 或是新的功能,而直接去利用開放類別的便利性去做修改,就是所謂的 “猴子補丁 (monkey patching)” 。
Ruby 有提供可以檢查是否有重複使用名稱的方法
[ ].methods.grep /^re/ # => 利用 regexp 找出所有re開頭的方法名稱
更詳細的介紹可以參考:
Inside the Object Model
“Where you learn surprising facts about objects, classes, and constants.” ― Metaprogramming Ruby ―
What is in an Object ?
class MyClass
def my_method
@v = 1
end
end
obj = MyClass.new
obj.class # => MyClass
每個實例物件都會有它的實例變數,加上ㄧ個指向屬於CLASS的連結。
OBJECT = INSTANCE VARIABLES + a REFERENCE to a CLASS
以下面的範例來說:這裡的 obj 是 MyClass 以 new 方法實例化的一個實例物件,而它的實例變數是 @v 以及指向 MyClass 的ㄧ個連結。
Instance Variables
在 Ruby 的世界裡,實例變數(instance variable)是住在實例物件(instance object)裡面,而方法(method)卻是住在類別(class)。延續先前上面的範例,可用以下方法從實例物件來查詢它的實例變數:
obj.my_method
obj.instance_variables # => [:@v]
有注意到是先呼叫 my_method() 而不直接呼叫 instance_variables 查詢實例變數。 原因是在 Ruby 程式設計裡,為了要讓都是屬於同一個 class 的 objects 都可以擁有自己不同的實例變數。 所以設計上物件(object)本身的類別(class)跟實例變數(instance variable)脫鉤。因此需要先呼叫 my_method() 方法 才能讓 @v=1 被定義。
class MyClass
def my_method
@v = 1
end
def my_name(name='default')
@name = name
end
end
obj1 = MyClass.new
obj2 = MyClass.new
obj1.class # => MyClass
obj1.instance_variables # => [], 實例變數還未定義
obj1.my_method # => 1, 呼叫my_method後,實例變數就被定義
obj1.instance_variables # => [:@v]
obj1.my_name('Jean') # => Jean
obj2.my_name # => default
obj2.my_name('Kevin') # => Kevin
Methods
Objects 物件除了有實例變數外,也擁有在 class 內的許多方法可以使用。書中有介紹可查詢物件所擁有的方法:
obj1.class.superclass # => Object
# 因為obj1從'Object'繼承了許多方法,所以回傳一堆obj1可使用的方法,
obj1.methods
# 利用regexp找出所有my開頭的方法
obj1.methods.grep(/my/) # => [:my_method, :my_name]
# 可以找出MyClass的實例方法, 帶入false參數是要過濾掉從'Object'繼承的方法
obj1.class.instance_methods(false) # => [:my_method, :my_name]
” An object’s instance variables live in the object itself, and an object’s methods live in the object’s class. “― Metaprogramming Ruby ―
注意:雖然 實例方法 instance methods 是需要實例化的物件(例如: obj1)才能使用,但是實例方法是住在類別(例如: MyClass)裡面
The Truth About Classes
” Here is possibly the most important thing you’ll never learn about the Ruby object model: classes themselves are nothing but objects.”― Metaprogramming Ruby ―
在 Ruby Object model 裡,類別 (Class) 其實也是實例化的物件(Object) 。
"Kevin".class # => String Class
String.class # => Class
先前提過的物件 (Object) 特性: 實例變數是住在實例物件裡面,而方法卻是住在類別
實例物件 (instance object) 的方法是定義在類別 (Class) 裡,而 Class 也是實例化的物件。因此 這些方法也是該類別 (Class) 的實例方法 (instance methods)。
"Kevin".class # => String Class
String.class # => Class
Class.class # => Class
Class.instance_methods(false) # => [:allocate, :superclass, :new]
從 Class.instance_methods(false) 可得知屬於類別自己而不是繼承來的方法為:allocate、superclass、 new 。其中最常見的就是 new 方法了,而 superclass 方法則是用來取得繼承的父類別
Module (模組)
也許讓很多人驚訝的是類別其實是繼承自模組,也就是說每個 class 也都是 module。
Class.superclass # => Modul
Class.instance_methods(false) # => [:allocate, :superclass, :new]
還記得剛剛提到的 3 個 Class 的實例方法:[:allocate, :superclass,:new],我們可以這樣說 Class 就是繼承自 Module 再加上 3 個以上實例方法。在許多的情況下,選擇使用 Class 還是 Module 的界線並不是那麼清楚。通常可以依照該程式碼區塊是否有擴展、繼承或是有實例化的需求,來作為考量的基礎。
Constant (常數)
在 Ruby 的世界中,類別與模組都必須以常數命名,其規定也很簡單,就是必須以英文大寫開頭。 嚴格來說,其實常數與變數的差別並不是很顯著,因為常數也是可以被修改的(只是會收到警告訊息而已!) 。兩者之間最重要的不同之處在於它們的作用範圍。由於 Ruby 編排所有常數的方式類似我們熟悉的檔案系統:類別或是模組比擬資料夾,而常數就相同於檔案。 以上面程式碼來說,常數的編排方式會如下圖: 即使以常數 Favorite 來命名不同的 2 個影片名稱 , 如果我要看美國的限制級戰警,就需要點選在USA資料夾內的 Favorite 影片才是正確的, 如果點到JP資料夾內的 Favorite 影片檔,就會是別的影片了。
在 Ruby 中常數也是如此,如要得到 “Triple X” 值,就必須得以『路徑』來區分並取得 Favorite 常數的值。 在 irb 裡便可看出需要以標示路徑 MyMovie::USA::Favorite 來取得正確的值。
重點回顧
- Open Class 增加了在撰寫程式的靈活性,也是許多人喜愛 Ruby的特色之一。只要使用得當,會讓程式閱讀上更好,日後在維護程式也將更有效率。
- 每個物件都有屬於它的實例變數,加上ㄧ個指向屬於CLASS的連結
- 實例變數是住在實例物件裡,方法是住在類別
- 類別也是物件
- 類別繼承自模組並擁有 3 個實例方法:new()、superclass()、allocate()
- 英文字母大寫開頭都是常數
- 常數是以自己獨特的路徑來區分及取值
Comments