让我们首先定义一些包含Enumerable模块的类的实例.
require 'set'
arr = [1, 2, 3]
hsh = { 1=>2, 2=>3, 3=>4 }
rng = (1..3).my_map { |n| n**2 }
set = (1..3).to_set
假设一个可枚举的方法定义如下.
module Enumerable
def reverse_em
a = []
each { |e| a.unshift(e) }
a
end
end
然后我们可以写道:
arr.reverse_em #=> [3, 2, 1]
hsh.reverse_em #=> [[:c, 3], [:b, 2], [:a, 1]]
rng.reverse_em #=> [3, 2, 1]
set.reverse_em #=> [3, 2, 1]
如您所见,四个不同类的实例使用单个Enumerable
方法.与为四个类中的每一个定义reverse_em
相比,这显然是一个更好的设计构造.
为了实现该功能,Array
、Hash
、Range
和Set
(以及其他核心类)的每个类定义必须具有语句include Enumerable
,并且必须包含返回枚举器(Array#each、Hash#each、Range#each和Set#each)的实例方法each
.
请注意,我的方法reverse_em
返回一个数组,而不考虑接收方的类.即使不是所有的Emumerable
个方法,也有很多个方法就是这样.
如果用户的定制类可以通过在类定义中包括语句include Enumerable
和返回枚举器的实例方法each
来利用Enumerable
模块中的所有方法.请注意,有几个Enumerable
个方法依赖于类要提供的each
以外的方法.
现在让我们来看第二个例子,其中Enumerable
方法必须考虑到传递块的可能性.具体地说,让我们考虑一下Enumerable#map是如何编码的.
module Enumerable
def my_map
return each unless block_given?
a = []
each { |e| a << yield(e) }
a
end
end
首先,如果没有传递任何块,则返回枚举器,该枚举器简单地枚举接收器的元素(与Enumerable#map
一样).
arr.my_map #=> #<Enumerator: [1, 2, 3]:each>
hsh.my_map #=> #<Enumerator: {1=>2, 2=>3, 3=>4}:each>
rng.my_map #=> #<Enumerator: 1..3:each>
set.my_map #=> #<Enumerator: #<Set: {1, 2, 3}>:each>
现在假设通过了一个块.
arr.my_map { |e| 2*e } #=> [2, 4, 6]
hsh.my_map { |k,v| k*v } #=> [2, 6, 12]
rng.my_map { |n| n**2 } #=> [1, 4, 9]
set.my_map { |n| n**2 } #=> [1, 4, 9]