通過使用鉤子方法,可以讓我們在Ruby的類或模組的生命週期中進行幹預,可以極大的提高編程的靈活性。
與生命週期相關的鉤子方法有下面這些:
類與模組相關
- Class#inherited
- Module#include
- Module#prepended
- Module#extend_object
- Module#method_added
- Module#method_removed
- Module#method_undefined
單件類相關
- BasicObject#singleton_method_added
- BasicObject#singleton_method_removed
- BasicObject#singleton_method_undefined
範例程式碼
module M1 def self.included(othermod) puts “M1 was included into #{othermod}” endendmodule M2 def self.prepended(othermod) puts “M2 was prepended to #{othermod}” endendclass C include M1 include M2end# 輸出M1 was included into CM2 was prepended to Cmodule M def self.method_added(method) puts “New method: M##{method}” end def my_method; endend# 輸出New method: M#my_method
除了上面列出來的一些方法外,也可以通過重寫父類的某個方法,進行一些過濾操作後,再通過調用super方法完成原函數的功能,從而實作類別似鉤子方法的功效,如出一轍,環繞別名也可以作為一種鉤子方法的替代實現。
運用執行個體
任務描述:
寫一個操作方法類似attr_accessor的attr_checked的類宏,該類宏用來對屬性值做檢驗,使用方法如下:
class Person include CheckedAttributes attr_checked :age do |v| v >= 18 endendme = Person.newme.age = 39 #okme.age = 12 #拋出異常
實施計劃:
使用eval方法編寫一個名為add_checked_attribute的核心方法,為指定類添加經過簡單校正的屬性
重構add_checked_attribute方法,去掉eval方法,改用其它手段實現
添加代碼塊校正功能
修改add_checked_attribute為要求的attr_checked,並使其對所有類都可用
通過引入模組的方式,只對引入該功能模組的類添加attr_checked方法
Step 1
def add_checked_attribute(klass, attribute) eval " class #{klass} def #{attribute}=(value) raise 'Invalid attribute' unless value @#{attribute} = value end def #{attribute}() @#{attribute} end end "endadd_checked_attribute(String, :my_attr)t = "hello,kitty"t.my_attr = 100puts t.my_attrt.my_attr = falseputs t.my_attr
這一步使用eval方法,用class和def關鍵詞分別開啟類,且定義了指定的屬性的get和set方法,其中的set方法會簡單的判斷值是否為空白(nil 或 false),如果是則拋出Invalid attribute異常。
Setp 2
def add_checked_attribute(klass, attribute) klass.class_eval do define_method "#{attribute}=" do |value| raise "Invaild attribute" unless value instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get "@#{attribute}" end endend
這一步更換掉了eval方法,同時也分別用class_eval和define_method方法替換了之前的class與def關鍵字,執行個體變數的設定和擷取分別改用了instance_variable_set和instance_variable_get方法,使用上與第一步沒有任何區別,只是一些內部實現的差異。
Step 3
def add_checked_attribute(klass, attribute, &validation) klass.class_eval do define_method "#{attribute}=" do |value| raise "Invaild attribute" unless validation.call(value) instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get "@#{attribute}" end endendadd_checked_attribute(String, :my_attr){|v| v >= 180 }t = "hello,kitty"t.my_attr = 100 #Invaild attribute (RuntimeError)puts t.my_attrt.my_attr = 200puts t.my_attr #200
沒有什麼奇特的,只是加了通過代碼塊驗證,增加了校正的靈活性,不再僅僅局限於nil和false之間了。
Step 4
class Class def attr_checked(attribute, &validation) define_method "#{attribute}=" do |value| raise "Invaild attribute" unless validation.call(value) instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get "@#{attribute}" end endendString.add_checked(:my_attr){|v| v >= 180 }t = "hello,kitty"t.my_attr = 100 #Invaild attribute (RuntimeError)puts t.my_attrt.my_attr = 200puts t.my_attr #200
這裡我們把之前頂級範圍中方法名放到了Class中,由於所有對象都是Class的執行個體, 所以這裡定義的執行個體方法,也能被Ruby中的其它所有類訪問到,同時在class定義中,self就是當前類,所以也就省去了調用類這個參數和class_eval方法,並且我們把方法的名字也改成了attr_checked。
Step 5
module CheckedAttributes def self.included(base) base.extend ClassMethods endendmodule ClassMethods def attr_checked(attribute, &validation) define_method "#{attribute}=" do |value| raise "Invaild attribute" unless validation.call(value) instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get "@#{attribute}" end endendclass Person include CheckedAttributes attr_checked :age do |v| v >= 18 endend
最後一步通過鉤子方法,在CheckedAttributes模組被引入後,對當前類通過被引入模組進行擴充, 從而使當前類支援引入後的方法調用,即這裡的get與set方法組。
到此,我們已經得到了一個名為attr_checked,類似attr_accessor的類宏,通過它你可以對屬性進行你想要的校正。