本帖已被设为精华帖!
Ruby有许多元编程的特性,到底什么时候该用呢?这是个值得思考的问题
在不该用的地方使用这些特性,只能影响程序的可维护性,违反了写代码的蠢萌原则(Kiss)
其实Rails里,就有大量使用元编程的例子。总结下来,主要目的有这么几种:
一、Rails中元编程的使用场景
1.1 简化接口
在刚开始学习Rails的时候,我常常被Rails dsl的表达能力给震惊到
before_action :require_no_sso!, only: [:new, :create]
has_many :users, through: :team_users
resources :teams
...
短短几个单词能够干的事情很多,而为了支持这样的语法,就要在dsl的原方法里使用元编程来动态定义和调用一些方法
这种极简化接口的特点就是,只用最少数的语句描述关键行为,将实现的细节完全隐藏,达到代码的高度简洁
1.2 让代码更加语义化
比如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')
1.3 Warning & Patch
比如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
1.4 DRY,减少重复性代码
如果要用相同的逻辑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
1.5 实现功能性需求
有些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提供支撑,让我们可以写更少的代码,写更明白的代码。