取得 Method


Ruby中,方法本身並不直接以物件方式存在,然而可以透過method方法取得Method實例。例如:
>> class Some
>>     def some
>>         puts "#{self}'s some method"
>>     end
>> end
=> nil
>> s = Some.new
=> #<Some:0x266b470>
>> mth = s.method(:some)
=> #<Method: Some#some>
>> mth.call
#<Some:0x266b470>'s some method
=> nil
>>


可以看到,取得的Method實例,self綁定的物件是s,可以取得Method實例的應用之一,就是當你的物件公開介面不一致,但又想以統一方式取得呼叫結果時。例如:
class Some
def initialize(value)
@value = value
end
def doSome(value)
@value - value
end
end

class Other
def initialize(amount)
@amount = amount
end
def doOther(amount)
@amount - amount
end
end

def utility(p, mth)
mth.call(p)
end

s = Some.new(100)
o = Other.new(200)

puts utility(10, s.method(:doSome)) # 90
puts utility(10, o.method(:doOther)) # 190

Some的doSome方法與Other的doOther方法,都是接受一個引數並傳回運算值,雖然方法介面不同,但utility方法仍可以進行呼叫運算。

以method方法取得的Method實例,self預設有綁定物件,可以使用unbind方法解除self的綁定(會取得UnboundMethod實例),使用bind方法再度綁定self的物件。例如:
class Some
def initialize(value)
@value = value
end
def doSome(value)
@value - value
end
end

s1 = Some.new(10)
s2 = Some.new(20)

puts s1.doSome(5) # 5

unbind_mth = s1.method(:doSome).unbind
puts unbind_mth.bind(s2).call(5) # 15

method可以取得實例上可呼叫的方法,包括單例方法,如果你想直接取得未綁定self的實例方法,也可以使用每個類別都有的instance_method方法(這個方法無法取得單例方法)。例如:
class Some
def initialize(value)
@value = value
end
def doSome(value)
@value - value
end
end

s1 = Some.new(10)
s2 = Some.new(20)

unbind_mth = Some.instance_method(:doSome)
puts unbind_mth.bind(s1).call(5) # 5
puts unbind_mth.bind(s2).call(5) # 15
   
bind可以綁定的對象,必須是同一類別或子類別實例(但無法綁定單例方法),它甚至可以作到從子類別實例呼叫父類別中已被重新定義的方法。例如:
class Some
def initialize(value)
@value = value
end
def doSome(value)
@value - value
end
end

class C_Some < Some
def doSome(value)
@value + value
end
end

s = Some.new(10)
c_s = C_Some.new(20)

unbind_mth = Some.instance_method(:doSome)
puts unbind_mth.bind(s).call(5) # 5
puts unbind_mth.bind(c_s).call(5) # 15,而不是 25,因為呼叫了父類別的 doSome
puts c_s.doSome(5) # 25

這感覺有點違反物件導向中多型的概念,一般來說,既然你已重新定義了方法,操作子類別實例的方法時應該就是被重新定義的方法,而不是父類別方法。

不過這也開啟了另一個功能性,因為Ruby中無法限制某個類別無法被繼承或無法被重新定義,為了確認執行某方法時,該方法一定是沒被重新定義過的方法,就可以使用這種功能性。例如:
class Some
def initialize(value)
@value = value
end
def doSome(value)
@value - value
end
end

# 一定呼叫Some的doSome
def do_some(s, v)
mth = Some.instance_method(:doSome).bind(s)
mth.call(v)
end

class C_Some < Some
def doSome(value)
@value + value
end
end

puts do_some(Some.new(10), 5) # 5
puts do_some(C_Some.new(20), 5) # 15

如果願意,你也可以呼叫to_proc將一個Method轉換為lambda。例如:
>> class Some
>>     def initialize(value)
>>         @value = value
>>     end
>>     def doSome(value)
>>         @value - value
>>     end
>> end
=> nil
>> s = Some.new(10)
=> #<Some:0x2727618 @value=10>
>> lda = s.method(:doSome).to_proc
=> #<Proc:0x2751d50 (lambda)>
>> lda.call(5)
=> 5
>>


一個應用的例子,可以在 建構、初始與消滅 中看到:
class Some
def initialize(value)
@value = value
ObjectSpace.define_finalizer(self,
self.method(:finalize).to_proc)
end
def finalize(object_id)
puts "Destroy #{object_id} Some(#{@value})...."
end
end

Some.new(10)
Some.new(20)
Some.new(30)

ObjectSpace.garbage_collect # 提示 GC

執行結果如下:
Destroy 16096056 Some(30)....
Destroy 16096140 Some(20)....
Destroy 16096224 Some(10)....