http://ruby-doc.org/stdlib-2.5.0/libdoc/forwardable/rdoc/Forwardable.html

Forwardable

Forwardable 模块使用 def_delegatordef_delegators 方法, 为指定对象提供特定方法的代理.

例如, 你有一个 RecordCollection 的类, 包含实例变量 @records.

你可以提供一个查表方法 record_number, 来在数组 @records 上调用 [], 如下:

require 'forwardable'

class RecordCollection
  attr_accessor :records
  extend Forwardable
  def_delegator :@records, :[], :record_number
end

我们可以这样使用查表方法:

r = RecordCollection.new
r.records = [4,5,6]
r.record_number(0)  # => 4

如果你想把 size << map 都代理给 @record, 可以这样:

class RecordCollection # re-open RecordCollection class
  def_delegators :@records, :size, :<<, :map
end

r = RecordCollection.new
r.records = [1,2,3]
r.record_number(0)   # => 1
r.size               # => 3
r << 4               # => [1, 2, 3, 4]
r.map { |x| x * 2 }  # => [2, 4, 6, 8]

你甚至可以用 Forwardable 扩展标准对象:

my_hash = Hash.new
my_hash.extend Forwardable              # prepare object for delegation
my_hash.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
my_hash.puts "Howdy!"

其他的例子:

我们希望重用之前的实现, 通过代理, 我们可以只使用一些需要的方法并适当地重命名他们.

在很多情况下, 用代理比继承要好, 继承得到的方法太多了, 很多是我们不需要的.

class Queue
  extend Forwardable

  def initialize
    @q = [ ]    # prepare delegate object
  end

  # setup preferred interface, enq() and deq()...
  def_delegator :@q, :push, :enq
  def_delegator :@q, :shift, :deq

  # support some general Array methods that fit Queues well
  def_delegators :@q, :clear, :first, :push, :shift, :size
end

q = Queue.new
q.enq 1, 2, 3, 4, 5
q.push 6

q.shift    # => 1
while q.size > 0
  puts q.deq
end

q.enq "Ruby", "Perl", "Python"
puts q.first
q.clear
puts q.first

输出:

2
3
4
5
6
Ruby
nil

提示: RDoc 不会标注被代理的方法.

SingleForwardable

SingleForwardable 可以用来设置对象层级上的代理.

printer = String.new
printer.extend SingleForwardable        # prepare object for delegation
printer.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
printer.puts "Howdy!"

SingleForwardable 也能用来给类和模块设置代理:

class Implementation
  def self.service
    puts "serviced!"
  end
end

module Facade
  extend SingleForwardable
  def_delegator :Implementation, :service
end

Facade.service #=> serviced!

如果你需要同时使用 ForwardableSingleForwardable, 最好明确调用 def_instance_delegatordef_single_delegator .

DIY 例子

class Order
  def initialize(origin_price, coupon)
    @origin_price = origin_price
    @coupon = coupon
  end

  def settle
    puts self.class
    @origin_price - @coupon
  end

  def discount_percent
    (1.0 * settle / @origin_price).round(2)
  end

  def self.printer
    "#{self}"
  end
end

require 'forwardable'
class OrderHistory
  extend Forwardable
  extend SingleForwardable

  def initialize
    @order = Order.new(100, 12)
  end

  def_instance_delegator :@order, :settle, :settlement
  # def_delegator :@order, :settle, :settlement

  def_instance_delegators :@order, :settle, :discount_percent
  # def_delegators :@order, :settle, :discount_percent

  # delegate [:settle, :discount_percent] => :@order

  def_single_delegator :Order, :printer, :printer2
end

oh = OrderHistory.new

puts oh.settlement # 88
puts oh.settle # Order 88
puts oh.discount_percent # 0.88

puts OrderHistory.printer2 # Order

需要特别注意的是, 调用代理方法的时候, self.class 为原类.