Ruby 有许多元编程的特性,到底什么时候该用呢?这是个值得思考的问题
在不该用的地方使用这些特性,只能影响程序的可维护性,违反了写代码的蠢萌原则 (Kiss)
其实 Rails 里,就有大量使用元编程的例子。总结下来,主要目的有这么几种:
在刚开始学习 Rails 的时候,我常常被 Rails dsl 的表达能力给震惊到
before_action :require_no_sso!, only: [:new, :create]
has_many :users, through: :team_users
resources :teams
...
短短几个单词能够干的事情很多,而为了支持这样的语法,就要在 dsl 的原方法里使用元编程来动态定义和调用一些方法
这种极简化接口的特点就是,只用最少数的语句描述关键行为,将实现的细节完全隐藏,达到代码的高度简洁
比如 1.day.ago 这种完全可以读出来的代码,要用 money patch 来实现。
(4.months + 5.years).from_now
各种 hack scope,动态扩展类,使原本需要用函数调用实现的功能,还是依然可以用优雅的链式调用来进行表达
比如 ActiveRecord 里的 wherechain,写起来十分的优雅,令人感到舒服
Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
比如 Api 变了,可以用 alias_method 来做个兼容
# *_action is the same as append_*_action
alias_method :"append_#{callback}_action", :"#{callback}_action"
define_method "append_#{callback}_filter" do |*names, &blk|
ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.")
send("append_#{callback}_action", *names, &blk)
end
ruby 的动态特性使得在进行版本兼容的时候可以更加随心所欲
if RUBY_VERSION > "..."
do something
end
如果要用相同的逻辑 wrapper 一些方法,或者注册一些逻辑相同的 helper 方法时,可以用元编程来减少代码量
%w(info debug warn error fatal unknown).each do |level|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{level}(progname = nil, &block)
logger.#{level}(progname, &block) if logger
end
METHOD
end
有些 api 本身就需要利用语言的动态特性,这时就不得不用了
def helper_method(*meths)
meths.flatten!
self._helper_methods += meths
meths.each do |meth|
_helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def #{meth}(*args, &blk)
controller.send(%(#{meth}), *args, &blk)
end
ruby_eval
end
end
再者,如果想扩展或者改变一些 ruby 语言的特性,就可以用到元编程
例如 autoload 和 ActiveSupport::Concern 或者 mattr_accessor 和 class_attribute 这样的功能,原生的 ruby 是不支持的,但是可以通过元编程来进行支持
当你想实现的功能本来就依赖 ruby 的动态特性时,就必须要使用元编程。
另外元编程可以为模块化、语义化和 Dsl 提供支撑,让我们可以写更少的代码,写更明白的代码。