通常情况下,我们使用关键字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/