使用define_method定义方法
这段代码中,Game类中的以runs_on开头的方法,可以用define_method重构, define_method
class Game
SYSTEMS = ['SNES', 'PS1', 'Genesis']
attr_accessor :name, :year, :system
def runs_on_snes?
self.system == 'SNES'
end
def runs_on_ps1?
self.system == 'PS1'
end
def runs_on_genesis?
self.system == 'Genesis'
end
end
重构后的代码,不必再定义一坨坨的方法
class Game
SYSTEMS = ['SNES', 'PS1', 'Genesis']
attr_accessor :name, :year, :system
SYSTEMS.each do |system|
define_method "runs_on_#{system.downcase}?" do
self.system = system
end
end
end
send 调用方法
ruby提供了send方法,可以实现对某个对象方法的调用,send方法中第一个参数是方法名,其余的是参数列表
library = Library.new(GAMES)
library.list
library.emulate("Contra")
game = library.find("Contra")
使用send方法就是这样,ruby还提供了public_send方法,public_send方法顾名思义只用于public的方法
library = Library.new(GAMES)
library.send(:list)
library.send(:emulate, "Contra")
game = library.send(:find, "Contra")
method对象
通常我们是这样调用方法的
library = Library.new(GAMES)
library.list
library.emulate("Contra")
也可以获得method对象,并使用method的call方法调用,比如上面的代码可以这样写,使用method方法获取一个Method对象
library = Library.new(GAMES)
list = library.method(:list)
list.call
emulate = library.method(:emulate)
emulate.call("Contra")
使用define_method和send重构
需重构的代码,需要把each,map,select重构,这几个方法非常的类似,可以用define_method代替
class Library
attr_accessor :games
def each(&block)
games.each(&block)
end
def map(&block)
games.map(&block)
end
def select(&block)
games.select(&block)
end
end
第一步,需要我们定义三个不同的方法,如下
class Library
attr_accessor :games
[:each, :map, :select].each do |method|
define_method method do |&block|
end
end
end
然后需要用send方法调用对象的不同的方法,可以这样写
define_method method do |&block|
game.send(method, &block)
end
重构完就是这样的样子
class Library
attr_accessor :games
[:each, :map, :select].each do |method|
define_method(method) do |&block|
games.send(method, &block)
end
end
end
method_missing
当调用一个对象不存在的方法时,会触发该对象的method_missing方法,比如
我们定义一个Library类,创建对象并调用test_method("arg1","arg2")方法,触发了一个
NoMethodError的异常
irb(main):001:0> class Library
irb(main):002:1> end
=> nil
irb(main):003:0> Library.new.test_method("arg1","arg2")
NoMethodError: undefined method `test_method' for #<Library:0x2745678>
from (irb):3
from D:/Ruby200/bin/irb:12:in `<main>'
可以覆盖method_missing方法,来捕获NoMethodError异常,比如
class Library
def method_missing(method_name, *args)
puts "call method: #{method_name}, args is: #{args}"
end
end
继续调用test_method方法,得到的结果
irb(main):013:0> Library.new.test_method("arg1","arg2")
call method: test_method, args is: ["arg1", "arg2"]
=> nil
method_missing, define_method和send的例子
实现Library类中的method_missing方法,使用define_method和method_missing动态的定义方法,需要定义的实例方法在SYSTEM数组里,使用self.class.class_eval动态的添加实例方法,define_method定义方法,比如调用librar.pc时,触发method_missing,pc在SYSTEM数组里,然后定义pc方法,并调用find_by_system方法
class Library
SYSTEMS = ['arcade', 'atari', 'pc']
attr_accessor :games
def method_missing(name, *args)
system = name.to_s
if SYSTEMS.include?(system)
self.class.class_eval do
define_method(system) do
find_by_system(system)
end
end
send(system)
else
super
end
end
private
def find_by_system(system)
games.select { |game| game.system == system }
end
end
respond_to?
在ruby中,可以使用respond_to?判断某个对象是否可以响应某个方法
比如"hello" 能够响应upcase方法
irb(main):017:0> "hello".respond_to?(:upcase)
=> true
当然,我们也可以自己定义respond_to?方法,比如Library类例子中,我们动态的定义了'arcade', 'atari', 'pc'方法,但是Library类的实例是不会响应动态定义的方法的。我们需要自己定义Library类的respond_to?方法
代码
def respond_to?(name)
SYSTEMS.include?(name.to_s) || super
end
当方法名在SYSTEMS数组是,返回true,代表响应相应的方法
other posts
- http://www.alfajango.com/blog/method_missing-a-rubyists-beautiful-mistress/
- http://ruby-china.org/topics/3434
- http://ruby-china.org/topics/4313