這段代碼中,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