Skip to content

Instantly share code, notes, and snippets.

@nightire
Last active March 29, 2018 09:01
Show Gist options
  • Save nightire/5221817 to your computer and use it in GitHub Desktop.
Save nightire/5221817 to your computer and use it in GitHub Desktop.
善用 define_method

善用 define_method

define_method 可以帮助我们动态的,快速的定义多个方法;比如有这样一个类:

class Post
  attr_accessor :title, :content, :state

  def initialize(title, content, state = :draft)
    @title, @content, @state = title, content, state
  end

  def draft
    @state = :draft
  end

  def posted
    @state = :posted
  end

  def deleted
    @state = :deleted
  end
end

draft posted deleted 分别用来把文章的状态设置为: 草稿 发表删除 ,但是这三个方法看起来太类似了,而 define_method 就可以帮助我们优化这部分代码:

class Post
  attr_accessor :title, :content, :state

  def initialize(title, content, state = :draft)
    @title, @content, @state = title, content, state
  end

  states = [:draft, :posted, :deleted]

  states.each do |state|
    define_method state do
      @state = state
    end
  end

这下就轻松多了吧!再来一个稍微复杂一点的例子,可以体现其灵活性:

class Game
  attr_accessor :name, :year, :system

  SYSTEMS = ['SNES', 'PS1', 'Genesis']

  SYSTEMS.each do |system|
    define_method "runs_on_#{system.downcase}?" do
      self.system = system.downcase
    end
  end
end

那么,如何在目标方法之后传递参数呢?比如说代理一个 block?

class Book
# 省略不相关代码

  [:each, :map, :select].each do |method|
    define_method method do |&block|
      books.send(method, &block)
    end
  end
end

方法体中之所以使用 send 方法,是因为在我们要重新定义的 each 方法里,books 需要委托数组方法 each 来处理 &block,而不是由 books 直接处理 █当然,其它两个方法也是这样的道理。最终 Ruby 帮我们生成的方法就是类似这样的:

def each # 这是我们为 Book 类定义的 each 方法
  books.each(&block) # 这是委托数组的 each 方法来帮 books 处理传入的 &block
end
@hiveer
Copy link

hiveer commented Mar 29, 2018

言简意赅,但是有一点笔者并没有展开讨论的就是,define_method究竟何时该用,用于解决什么问题?
我有此疑问是因为,我某些场景下,我很迷惑时用method_missing 还是 define-method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment