Ruby 中的 forwardable 模块 ( 上 )

yufei       5 年, 8 月 前       1660

Ruby 语言中有许多非常有趣的实现,比如我们在 Ruby 中的 class_eval 和 module_eval 方法 章节中讲解的 class_evalmodule_eval 两个方法用于在运行时为类和模块动态的添加方法和属性。

本章节,我们就来讲讲另一个模块 forwardable,它用于给给定类的所有实例添加一个行为。

本章节,我们将涉及到以下几个知识点

  • Forwardable 模块
  • def_delegator 方法
  • def_delegators 方法
  • delegate 方法

Forwardable 模块

Forwardable 是一个模块,包含了一些方法可用于向给定类的所有实例添加行为 ( behavior )

在 Ruby 内部实现时,使用了 extend 关键字将此模块包含进 singleton 类,为此可以在类级别向类添加方法 ( 保持简单 )

接下来,让我们来阐述下 Forwardable 模块的 API 吧。

def_delegator 方法

Forwardable#def_delegator 方法允许对象将消息转发到定义的接收器

如果您不熟悉 Ruby 中的消息和接收器的概念,可以阅读我们的文章 Ruby 中 private 和 protected 的消息 ( Message ) 机制

揭开先前论调的神秘面纱,没有什么比举例更好的了

# in forwardable.rb
class Hero
  attr :skills
  def initialize
    @skills = [:strong, :keen, :brave]
  end
end

jack = Hero.new
puts "Jack's main skill: #{jack.skills.first}"

运行结果如下

?> ruby forwardable.rb
Jack's main skill: strong

这能正常工作,但, 在 Hero 类定义之外调用 jack.skills.first 有点..像个怪人

因此,让我们将此代码封装到 Hero 类定义中

# in forwardable.rb
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.rb
Jack's main skill: strong

棒极了!代码中的 Hero#main_skill 方法包含访问 Hero 主技能的逻辑

这个解决方案是可以接受的。

但 Ruby 提供了一种更好的机制,使用 Forwardable#def_delegator 方法可以将消息( #first )从实例( jack ) 转发到显式接收器(技能)

我们就按照这种机制修改下代码呗

# in forwardable.rb
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}"

输出结果如下

?> ruby forwardable.rb
Jack's main skill: strong

太酷了!这里,我们通过使用 Ruby 提供的消息转发系统避免了创建一个 getter 方法来访问技能

上面的代码中

  1. 首先,我们加载了 forwardable 模块
  2. 其次,我们使用 extend Forwardable 关键字将该模块的方法添加到 Hero 类级别中
  3. 最后,我们使用新添加的类级方法 def_delegator
    • 第一个参数 :@skills 对应于消息转发的接收者。
    • 第二个参数 :first 是要转发的消息
    • 最后是第三个参数 :main_skill:first 消息的别名 - 当我们调用 jack.main_skill 时, 它比 jack.first 更可读 - 然后在内部自动调用 skills.first

def_delegators 方法

def_delegators 方法与 def_delegator 方法类似,

两个方法的主要区别是,def_delegators 方法需要一组方法来转发,并且方法不能别名

# in forwardable.rb
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}"

输出结果为

?> ruby forwardable.rb
first tasks: conception
last  tasks: refactoring

在这个示例中,tasks 数组的 firtlast 方法可用于任何 Todolis 实例

当调用这两个方法其中之一时,消息将被转发到 tasks 数组。

delegate 方法

delegate 方法接受一个散列 ( hash ) 作为参数,其中

  • 键 ( key ) 是一条或多条消息
  • 值 ( value ) 是 键对应的消息的接收器
# in forwardable.rb
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}"

输出结果为

$> ruby forwardable.rb
Cores:   8
Screens: 2

上面的示例中,macrosoft.size 消息对应于 macrosoft.cores.size

并且,macrosoft.length 消息对应于 macrosoft.screens.length

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.