Ruby methods notes

使用define_method定義方法

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

send 調用方法

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")

method對象

一般咱們是這樣調用方法的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")

使用define_method和send重構

需重構的代碼,須要把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

當調用一個對象不存在的方法時,會觸發該對象的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

相關文章
相關標籤/搜索