Ruby如何實現動態方法調用?

在Ruby中,有多種方法可以實現方法的動態調用。
1. 使用send方法
第一種實現動態方法調用是使用send方法,send方法在Object類中定義,方法的第一個參數是一個符號用來表示所要調用的方法,後面則是所調用方法需要的參數。
“This is a dog1″.send(:length) => 14
上面的代碼中通過send方法去對一個字符串執行length操作,返回字符串的長度。

class TestClass
 def hello(*args)
  ”Hello ” + args.join(‘ ‘)
 end
end

a = TestClass.new
puts a.send :hello, “This”, “is”, “a”, “dog!”

執行結果爲:
Hello This is a dog!

2. 使用Method類和UnboundMethod類
另一種實現動態方法調用是使用Object類的method方法,這個方法返回一個Method類的對象。我們可以使用call方法來執行方法調用。
test1 = “This is a dog1″.method(:length)
test1.call  => 14

class Test
 def initialize(var)
  @var = var
 end
 
 def hello()
  ”Hello, @var = #{@var}”
 end
end

k = Test.new(10)
m = k.method(:hello)
m.call   #=> “Hello, @iv = 99″

l = Test.new(‘Grant’)
m = l.method(“hello”)
m.call   #=> “Hello, @iv = Fred”

可以在使用對象的任何地方使用method對象,當調用call方法時,參數所指明的方法會被執行,這種行爲有些像C語言中的函數指針。你也可以把method對象作爲一個迭代器使用。
def square(a)
 a*a
end

mObj = method(:square)
[1, 2, 3, 4].collect(&mObj)  => [1 4 9 16]

Method對象都是和某一特定對象綁定的,也就是說你需要通過某一對象使用Method對象。你也可以通過UnboundMethod類創建對象,然後再把它綁定到某個具體的對象中。如果UnboundMethod對象調用時尚未綁定,則會引發異常。
class Double
 def get_value
  2 * @side
 end
 
  def initialize(side)
   @side = side
  end
end

a = Double.instance_method(:get_value) #返回一個UnboundMethod對象
s = Double.new(50)
b = a.bind(s)
puts b.call

執行結果爲:
100

看下面一個更具體的例子:
class CommandInterpreter
def do_2() print “This is 2\n”; end
 def do_1() print “This is 1\n”; end
 def do_4() print “This is 4\n”; end
 def do_3() print “This is 3\n”; end
  
 Dispatcher = {
   ?2 => instance_method(:do_2),
   ?1 => instance_method(:do_1),
   ?4 => instance_method(:do_4),
   ?3 => instance_method(:do_3)
 }

 def interpret(string)
   string.each_byte {|i| Dispatcher[i].bind(self).call }
  end
end

interpreter = CommandInterpreter.new
interpreter.interpret(’1234′)

執行結果爲:
This is 1
This is 2
This is 3
This is 4

3. 使用eval方法
我們還可以使用eval方法實現方法動態調用。eval方法在Kernel模塊中定義,有多種變體如class_eval,module_eval,instance_eval等。Eval方法將分析其後的字符串參數並把這個字符串參數作爲Ruby代碼執行。
str = “Hello”
eval “str + ‘ World!’” => Hello World!

sentence = %q{“This is a test!”.length}
eval sentence => 15
當我們在使用eval方法時,我們可以通過eval方法的第二個參數指明eval所運行代碼的上下文環境,這個參數可以是Binding類對象或Proc類對象。Binding類封裝了代碼在某一環境運行的上下文,可以供以後使用。
class BindingTest
 def initialize(n)
   @value = n
 end

 def getBinding
   return binding() #使用Kernel#binding方法返回一個Binding對象
  end
end

obj1 = BindingTest.new(10)
binding1 = obj1.getBinding
obj2 = BindingTest.new(“Binding Test”)
binding2 = obj2.getBinding

puts eval(“@value”, binding1)  #=> 10
puts eval(“@value”, binding2)  #=> Binding Test
puts eval(“@value”)     #=> nil

可以看到上述代碼中,@value在binding1所指明的上下文環境中值爲10,在binding2所指明的上下文環境中值爲Binding Test。當eval方法不提供binding參數時,在當前上下文環境中@value並未定義,值爲nil。

發佈了42 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章