这段代码中,Game类中的以runs_on开头的方法,能够用define_method重构, define_methodapi
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
ruby提供了send方法,能够实现对某个对象方法的调用,send方法中第一个参数是方法名,其他的是参数列表ruby
library = Library.new(GAMES) library.list library.emulate("Contra") game = library.find("Contra")
使用send方法就是这样,ruby还提供了public_send方法,public_send方法顾名思义只用于public的方法post
library = Library.new(GAMES) library.send(:list) library.send(:emulate, "Contra") game = library.send(:find, "Contra")
一般咱们是这样调用方法的code
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")
需重构的代码,须要把each,map,select重构,这几个方法很是的相似,能够用define_method代替blog
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
第一步,须要咱们定义三个不一样的方法,以下get
class Library attr_accessor :games [:each, :map, :select].each do |method| define_method method do |&block| end end end
而后须要用send方法调用对象的不一样的方法,能够这样写class
define_method method do |&block| game.send(method, &block) end
重构完就是这样的样子test
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方法,好比
咱们定义一个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
实现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
在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