2014年12月3日 星期三

[ Ruby Gossip ] Advance : 可執行物件 - 取得 Method

Source From Here
Preface
Ruby 中,方法本身並不直接以物件方式存在,然而可以透過 method 方法取得 Method 實例。例如:


取得 Method
可以看到,取得的 Method 實例,self 綁定的物件是 s,可以取得 Method 實例的應用之一,就是當你的物件公開介面不一致,但又想以統一方式取得呼叫結果時。例如:
  1. class Some  
  2.     def initialize(value)  
  3.         @value = value  
  4.     end  
  5.     def doSome(value)  
  6.         @value - value  
  7.     end  
  8. end  
  9.   
  10. class Other  
  11.     def initialize(amount)  
  12.         @amount = amount  
  13.     end  
  14.     def doOther(amount)  
  15.         @amount - amount  
  16.     end  
  17. end  
  18.   
  19. def utility(p, mth)  
  20.     mth.call(p)  
  21. end  
  22.   
  23. s = Some.new(100)  
  24. o = Other.new(200)  
  25.   
  26. puts utility(10, s.method(:doSome))   # 90  
  27. puts utility(10, o.method(:doOther))  # 190  
Some 的 doSome 方法與 Other 的 doOther 方法,都是接受一個引數並傳回運算值,雖然方法介面不同,但 utility 方法仍可以進行呼叫運算。以 method 方法取得的 Method 實例,self 預設有綁定物件,可以使用 unbind 方法解除 self 的綁定(會取得 UnboundMethod 實例),使用 bind 方法再度綁定 self 的物件。例如:
  1. class Some  
  2.     def initialize(value)  
  3.         @value = value  
  4.     end  
  5.     def doSome(value)  
  6.         @value - value  
  7.     end  
  8. end  
  9.   
  10. s1 = Some.new(10)  
  11. s2 = Some.new(20)  
  12.   
  13. puts s1.doSome(5) # 5  
  14.   
  15. unbind_mth = s1.method(:doSome).unbind  
  16. puts unbind_mth.bind(s2).call(5) # 15  
method 可以取得實例上可呼叫的方法,包括單例方法,如果你想直接取得未綁定 self 的實例方法,也可以使用每個類別都有的 instance_method 方法(這個方法無法取得單例方法)。例如:
  1. class Some  
  2.     def initialize(value)  
  3.         @value = value  
  4.     end  
  5.     def doSome(value)  
  6.         @value - value  
  7.     end  
  8. end  
  9.   
  10. s1 = Some.new(10)  
  11. s2 = Some.new(20)  
  12.   
  13. unbind_mth = Some.instance_method(:doSome)  
  14. puts unbind_mth.bind(s1).call(5) # 5  
  15. puts unbind_mth.bind(s2).call(5) # 15  
bind 可以綁定的對象,必須是同一類別或子類別實例但無法綁定單例方法),它甚至可以作到從子類別實例呼叫父類別中已被重新定義的方法。例如:
  1. class Some  
  2.     def initialize(value)  
  3.         @value = value  
  4.     end  
  5.     def doSome(value)  
  6.         @value - value  
  7.     end  
  8. end  
  9.   
  10. class C_Some < Some  
  11.     def doSome(value)  
  12.         @value + value  
  13.     end  
  14. end  
  15.   
  16. s = Some.new(10)  
  17. c_s = C_Some.new(20)  
  18.   
  19. unbind_mth = Some.instance_method(:doSome)  
  20. puts unbind_mth.bind(s).call(5)   # 5  
  21. puts unbind_mth.bind(c_s).call(5) # 15,而不是 25,因為呼叫了父類別的 doSome  
  22. puts c_s.doSome(5)                # 25  
這感覺有點違反物件導向中多型的概念,一般來說,既然你已重新定義了方法,操作子類別實例的方法時應該就是被重新定義的方法,而不是父類別方法。不過這也開啟了另一個功能性,因為 Ruby 中無法限制某個類別無法被繼承或無法被重新定義,為了確認執行某方法時,該方法一定是沒被重新定義過的方法,就可以使用這種功能性。例如:
  1. class Some  
  2.     def initialize(value)  
  3.         @value = value  
  4.     end  
  5.     def doSome(value)  
  6.         @value - value  
  7.     end  
  8. end  
  9.   
  10. # 一定呼叫Some的doSome  
  11. def do_some(s, v)   
  12.     mth = Some.instance_method(:doSome).bind(s)  
  13.     mth.call(v)  
  14. end  
  15.   
  16. class C_Some < Some  
  17.     def doSome(value)  
  18.         @value + value  
  19.     end  
  20. end  
  21.   
  22. puts do_some(Some.new(10), 5)   # 5  
  23. puts do_some(C_Some.new(20), 5) # 15  
如果願意,你也可以呼叫 to_proc 將一個 Method 轉換為 lambda。例如:


一個應用的例子,可以在 建構、初始與消滅 中看到:
  1. class Some  
  2.     def initialize(value)  
  3.        @value = value  
  4.        ObjectSpace.define_finalizer(self,  
  5.                                     self.method(:finalize).to_proc)  
  6.     end  
  7.     def finalize(object_id)  
  8.         puts "Destroy #{object_id} Some(#{@value})...."  
  9.     end  
  10. end  
  11.   
  12. Some.new(10)  
  13. Some.new(20)  
  14. Some.new(30)  
  15.   
  16. ObjectSpace.garbage_collect   # 提示 GC  
執行結果如下:
Destroy 16096056 Some(30)....
Destroy 16096140 Some(20)....
Destroy 16096224 Some(10)....


沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...