forwardable模块
最近在看rack-attack源码的过程中,看到了这个模块。ruby的语法糖实在是太丰富了,各种特性看得人一脸懵逼。幸好各位前辈留下了许多文章去给我们指点迷津,这篇文章对forwardable的描述我觉得很通俗异动,很适合我们对这个模块有一个比较浅显的理解。
Forwardable 是一个模块,中文名翻译为委托,包含了一些方法可用于向给定类的所有实例添加行为 ( behavior )。
在 Ruby 内部实现时,使用了 extend 关键字将此模块包含进 singleton 类,为此可以在类级别向类添加方法 ( 保持简单 )。简单来说,就是利用Ruby 提供的消息转发系统避免了创建一个 getter 方法来访问我们所需要的元素。
def_delegator 方法
在介绍forwardable的这些API方法之前,我们先举一些简单的例子来给看看我们为什要使用这些方法,观察一下这些方法给我带来了什么便利。
class Hero
attr :skills
def initialize
@skills = [:strong, :keen, :brave]
end
end
jack = Hero.new
puts "Jack's main skill: #{jack.skills.first}"
运行结果如下:
Jack's main skill: strong
这能正常工作,但, 在 Hero 类定义之外调用 jack.skills.first 稍微有点冗余,我们可以first方法进行一次封装。因此,让我们将此代码封装到 Hero 类定义中:
class Hero
attr :skills
def initialize
@skills = [:strong, :keen, :brave]
end
def main_skill
@skills.first
end
end
jack = Hero.new
puts "Jack's main skill: #{jack.main_skill}"
在这里,我们会得到和不封装前一样的输出。但是ruby提供了一种更优雅的机制,Forwardable#def_delegator 方法允许对象将消息转发到定义的接收器,我们现在先按照这种机制修改一下代码:
require 'forwardable'
class Hero
attr :skills
extend Forwardable
def_delegator :@skills, :first, :main_skill
def initialize
@skills = [:strong, :keen, :brave]
end
end
jack = Hero.new
puts "Jack's main skill: #{jack.main_skill}"
上面的代码中
- 首先,我们加载了 forwardable 模块
- 其次,我们使用 extend Forwardable 关键字将该模块的方法添加到 Hero 类级别中
- 最后,我们使用新添加的类级方法 def_delegator,我们的实例子jack是我们的对象,是我们的转发者。第一个参数 :@skills 对应于消息转发的接收者,第二个参数 :first 是要转发的消息,最后是第三个参数 :main_skill 是 :first 消息的别名。当我们调用 jack.main_skill 时, 它比 jack.first 更可读,然后在内部自动调用 skills.first。
def_delegators 方法
def_delegators 方法与 def_delegator 方法类似,两个方法的主要区别是,def_delegators 方法需要一组方法来转发,并且方法不能别名。
require 'forwardable'
class Todolist
attr :tasks
extend Forwardable
def_delegators :@tasks, :first, :last
def initialize
@tasks = %w[conception implementation refactoring]
end
end
todolist = Todolist.new
puts "first tasks: #{todolist.first}"
puts "last tasks: #{todolist.last}"
输出结果为:
first tasks: conception
last tasks: refactoring
在这个示例中,tasks 数组的 firt 和 last 方法可用于任何 Todolis 实例, 当调用这两个方法其中之一时,消息将被转发到 tasks 数组。
delegate 方法
delegate 方法接受一个散列 ( hash ) 作为参数,其中键 ( key ) 是一条或多条消息,
值 ( value ) 是 键对应的消息的接收器。
require 'forwardable'
class Computer
attr :cores, :screens
extend Forwardable
delegate %I[size] => :@cores,
%I[length] => :@screens
def initialize
@cores = (1..8).to_a
@screens = [1, 2]
end
end
macrosoft = Computer.new
puts "Cores: #{macrosoft.size}"
puts "Screens: #{macrosoft.length}"
输出结果为:
Cores: 8
Screens: 2
上面的示例中,macrosoft.size 消息对应于 macrosoft.cores.size,macrosoft.length 消息对应于 macrosoft.screens.length。