天天看点

译:Ruby动态方法

通常情况下,我们使用关键字def来定义方法。

但是当有一系列方法具有相同的结构和逻辑时,继续这样做未免显得有些重复并且违反了DRY原则。

Ruby,作为一个动态语言,允许你在运行时定义方法。

那么,这是什么意思呢?

来看一个最简单的例子:

class A
  define_method :a do
    puts "hello"
  end
  define_method :greeting do |message|
    puts message
  end
end

A.new.a #=> hello
A.new.greeting 'Ram ram' #=> Ram ram
           

define_method 用来定义实例方法,下面的例子展示更加贴合实际的使用场景。

class User
    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2
    attr_accessor :status

    def active?
      status == ACTIVE
    end

    def inactive?
      status == User::INACTIVE
    end

    def pending?
      status == User::PENDING
    end
  end

  user = User.new
  user.status = 1

  user.inactive?
  #=> true
  user.active?
  #=> false
           

使用动态方法定义重构:

class User
    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2
    attr_accessor :status

    [:active, :inactive, :pending].each do |method|
      define_method “#{method}?” do
        status == User.const_get(method.upcase)
      end
    end
  end

  user = User.new
  user.status = 1

  user.inactive?
  #=> true
  user.active?
  #=> false
           

我们使用了define_method来动态的定义方法。

我们也可以使用类方法来定义实例方法,利用这一点我们可以暴露一个用来生成实例方法的类方法。COOL!

例如:

class User
    ACTIVE = 0
    INACTIVE = 1
    PENDING = 2
    attr_accessor :status

    def self.states(*args)
      args.each do |arg|
        define_method “#{arg}?” do
          self.status == User.const_get(arg.upcase)
        end
      end
    end

    states :active, :inactive, :pending
  end
           

定义类方法最简单的方式则为:

class A
  class << self
    define_method method_name do
      #...
    end
  end
end
           

也可以使用 instance_eval和class_eval来动态定义方法。这些方法允许运行特定类或方法中的任意代码。这些方法有时候很让人迷惑,你可以看看 stackoverflow上的讨论或者读读博客来理解它们的用法。

stackoverflow上的讨论我们可以总结如下:

Foo = Class.new
  Foo.class_eval do
    def class_bar
      “class_bar”
    end
  end
  Foo.instance_eval do
    def instance_bar
      “instance_bar”
    end
  end
  Foo.class_bar       #=> undefined method ‘class_bar’ for Foo:Class
  Foo.new.class_bar   #=> “class_bar”
  Foo.instance_bar       #=> “instance_bar”
  Foo.new.instance_bar   #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>
           

注意,我们没有在_eval中使用*define_method,因为如下例所示,不管在class_eval还是instance_eval内使用define_method方法,它最终定义的都是实例方法。

Foo = Class.new
  Foo.class_eval do
    define_method “class_bar” do
      “class_bar”
    end
  end
  Foo.instance_eval do
    define_method “instance_bar” do
      “instance_bar”
    end
  end

  Foo.class_bar #=> undefined
  Foo.new.class_bar #=> “class_bar”
  Foo.instance_bar #=> undefined
  Foo.new.instance_bar #=> “instance_bar”
           

接下来,我们来看看方法的动态调用。Ruby中一个动态调用方法的方式是向对象发送消息。我们可以向类或类定义本身发送消息发送消息,或者只是发送给类对象。这些都可以通过调用send方法完成。

最简单的例子如下:

s= “hi man”

  p s.length #=> 6
  p s.include? “hi” #=> true

  p s.send(:length) #=> 6
  p s.send(:include?,”hi”) #=> true
           

这个方法在什么时候有用呢?

来看看下面这段代码(取例自:http://www.funonrails.com/2011/12/dynamic-methods-inside-ruby-classes.html)

class ApplicationController < ActionController::Base
    protect_from_forgery
    helper_method :current_staff, :current_employee, current_admin

    def authenticate_staff!(opts={})
      current_staff || not_authorized
    end

    def current_staff
      current_user if current_user.is_a? Staff
    end

    def authenticate_employee!(opts={})
      current_employee || not_authorized
    end

    def current_employee
      current_user if current_user.is_a? Employee
    end

    def authenticate_admin!(opts={})
      current_admin || not_authorized
    end

    def current_admin
      current_user if current_user.is_a? Admin
    end
  end
           

使用send方法重构:

%w(Staff Employee Admin).each do |k|
    define_method “current_#{k.underscore}” do
      current_user if current_user.is_a?(k.constantize)
    end

    define_method “authenticate_#{k.underscore}!” do |opts={}|
      send(“current_#{k.underscore}”) || not_authorized
    end
  end
           

动态方法定义能够减少方法定义的错误,减少重复并且简化代码

享受元编程吧~

原文:http://rohitrox.github.io/2013/07/02/ruby-dynamic-methods/