Forwardable
这个库我很喜欢, 它能够很大程度上简单代码,让我的库保持简洁. 通常我发现如果想试图去学习理解一个事物的存在就需要明白它的存在性和如果使用.
###如何使用
首先,我们来看看如何通过 Forwardable
这个模块来简单代码. 我经常发现一些莫须有的东西拖慢自己的速度, 而且很多时候存在重复的想法,当我在阅读一些代码的时候.
我们看下面的代码:
class Person
def street
address.street
end
def city
address.city
end
def state
address.state
end
end
看上面的代码,可能觉得很简单,但是你会发现他们其实在做同一件事情.也就是 Person.street
其实就是 address.street
如此而已.
ruby 有一个内置的方式来提炼这个代码.那就是 Forwardable
.
上面代码中的意思是给一个对象添加赋值语句, 这个情况下, 如果你要获取人的地址信息, 它要去到相关的地址对象. 这个过程相对简单, 但是我还是想换个方式来作, 看下面的代码:
require 'forwardable'
class Person
extend Forwardable
delegate [:street, :city, :state] => :address
上面代码的意思就是它把一个消息传递给了合作的对象, 看上去很清楚,感觉和读取配置文件似得. 简单明了.
Forwardable
可以工作在更长的代码段上, 你完全可以定义一个很长的方法 list, 然后把他们委托给对应的对象上. 我们可以通过下面的代码感受一下 Forwardable
是怎么工作的.
module Forwardable
def delegate(hash)
hash.each{ |methods, accessor|
methods.each{ |method|
instance_eval %{
def #{method}(*args, &block)
#{accessor}.__send__(:#{method}, *args, &block)
end
}
}
}
end
end
这个 delegate
方法接受一个 hash
对象, 他的 key
是对应的方法,然后它把这个方法传递给了对应的value
值,也就是对应的委托对象. 通过 instance_eval
把这个方法绑定到对象上. 但是实际上 Forwardable
的代码很复杂,它用了2个循环来处理一个 hash 和数组,之后再 instance_eval
. 通过遍历和动态方法定义就完成了重复的代码简化.
###模块和它的上下文
Forwardable
它是一个模块. 它可以用来给一个对象追加行为. 一般我们看到的实现这个功能的方式是:
class Person
include SuperSpecial
end
但是 Forwardable
不一样,它是用extend
来完成的.
require 'forwardable'
class Person
extend Forwardable
end
通过使用 extend
包含了模块到当前对象的 singleton_class
中. 这里可能有点绕,但是其实区别就在于 include
是追加实例方法, 而 extend
是追加类方法的. 总结一句话就是在使用 Forwadable
的时候咱们使用 extend
.
###定于传递规则
- 通常我喜欢使用的方式就是上面提到的
: delegate
方式,把一系类的方法名通过symbol
或者string
以 hash 的键传递给对应value
的对象上.
class Person
extend Forwardable
delegate [:message_to_forward, :another_method_name] => :object_to_receive_message,
:single_method => :other_object
end
- 其他的方式:
Forwardable
提供了很多方法. 但是最常见的就是 delegate
, def_delegator
和 def_delegators
. 但是他们也都是方法别名.
alias delegate instance_delegate
alias def_delegators def_instance_delegators
alias def_delegator def_instance_delegator
我们发现也就是 delegate
这个方法名最简单一点.
这个 def_delegators
方法接受多个参数, 这样就是不容易记住那些参数比较重要, 第一个参数是对应的我们要委托的对象. 第二个是我们要传递的方法.
class SpecialCollection
extend Forwardable
def_delegators :@collection, :clear, :first, :push, :shift, :size
# The above is equivalent to:
delegate [:clear, :first, :push, :shift, :size] => :@collection
end
通过上面可以发现,delegate
的方式参数最起码把要委托的方法名和委托方给隔离开, 看上去清晰一些.
还有一个区别的地方是:
def instance_delegate(hash) # aliased as delegate
hash.each{ |methods, accessor|
methods = [methods] unless methods.respond_to?(:each)
methods.each{ |method|
def_instance_delegator(accessor, method)
}
}
end
delegate
在接到参数之后,会先把方法名的参数验证是否指数组,如果不是还要转,然后再调用 def_instance_delegator
方法. 下面是def_instance_delegators
的实现:
def def_instance_delegators(accessor, *methods) # aliased as def_delegators
methods.delete("__send__")
methods.delete("__id__")
for method in methods
def_instance_delegator(accessor, method)
end
end
上面的代码中移除了比较危险的__ send__
和__ id__
方法,然后就直接调用def_instance_delegator
来完成了.
这里,如果你像一股脑儿的把所有的方法都委托了,直接可以这样:
class SpecialCollection
extend Forwardable
def_delegators :@collection, *Array.instance_methods
# The above is equivalent to:
delegate [*Array.instance_methods] => :@collection
end
这样的话,当然就包括了__ send__
方法进去了, ruby 会警告这个有危险.
def_delegators
是def_delegator
的复数形式, 它提供了更多的选项.
class SpecialCollection
extend Forwardable
def_delegator :@collection, :clear, :remove
def_delegator :@collection, :first
end
这里, def_delegator
接受3个参数,第一个是接收方,第二个是委托方法,第三个是可选的就是要定义在当前对象上的方法. 如果不提供,就直接取第二个.
class SpecialCollection
extend Forwardable
# def_delegator :@collection, :clear, :remove
def remove
@collection.clear
end
# def_delegator :@collection, :first
def first
@collection.first
end
end
上面的代码就是如果不适用 Forwardable
特点的代码. 可以发现, 第三个可选参数的区别.
###这些方法到底是怎么创造的
让我们来看看 Forwardable
到底是怎么添加放到你的当前对象的.
def def_instance_delegator(accessor, method, ali = method)
line_no = __LINE__; str = %{
def #{ali}(*args, &block)
begin
#{accessor}.__send__(:#{method}, *args, &block)
rescue Exception
$@.delete_if{|s| Forwardable::FILE_REGEXP =~ s} unless Forwardable::debug
::Kernel::raise
end
end
}
# If it's not a class or module, it's an instance
begin
module_eval(str, __FILE__, line_no)
rescue
instance_eval(str, __FILE__, line_no)
end
end
上面代码看上去很长,很复杂. 是的.. 让我们来简化它,
def def_instance_delegator(accessor, method, ali = method)
str = %{
def #{ali}(*args, &block)
#{accessor}.__send__(:#{method}, *args, &block)
end
}
module_eval(str, __FILE__, __LINE__)
end
我们看到,这个地方用 string 定义了一个方法,然后赋值给str
. 然后把它传给了 module_eval
, 虽然 module_eval
和class_eval
是类似的.但是我往往更多看家难道是 class_eval
啊.别管他, 把 class_eval
看成是module_eval
的别名好了.
让我们看看之前的一段代码的实现:
def_delegator :@collection, :clear, :remove
它其实对应于:
%{
def remove(*args, &block)
@collection.__send__(:clear, *args, &block)
end
}
###错误管理
###应用生成的方法
不要就是通过 module_eval
来把定义方法.
# If it's not a class or module, it's an instance
begin
module_eval(str, __FILE__, line_no)
rescue
instance_eval(str, __FILE__, line_no)
end
如果 module_eval
发生错误了, 它会执行instance_eval
. 这个地方我算是找到了 instance_eval
的用处了, 它也就是说不仅仅作用于类和模块,而且可以用于一个单独的对象.
object = Object.new
object.extend(Forwardable)
object.def_delegator ...
###类或者模块级别的委托
上面实现的方法他们都是给类实例的, 虽然有趣,但是就范围有限. 不过别担心 forwardable.rb
同样提供了一个 SingleForwardable
, 他们是为模块专门设计的.(类也是模块!)
class Person
extend Forwardable
extend SingleForwardable
end
上面的代码中你可以看到它同时扩展了 Forwardable
和 SingleForwardable
这也就意味着它可以同时把委托的方法供给类实例和类本身.
这个用法也就类似 def_instance_delegator
替代 def_delegator
. 如果使用 def_delegator
,这些方法不会给别名. 你应该如下面的方法来使用:
class Person
extend Forwardable
extend SingleForwardable
single_delegate [:store_exception] => :ExceptionTracker
instance_delegate [:street, :city, :state] => :address
end
如果你仔细想, 你会发现他们的区别在于:
alias delegate single_delegate
alias def_delegators def_single_delegators
alias def_delegator def_single_delegator
也就是如果你想用细了这个 instance_delegate
和 single_delegate
就不要省略他们了要写明了这个区别.