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

相关文章
相关标签/搜索