如何将"each"方法添加到Ruby对象(或者我应该扩展Array)?

Pou*_*oul 40 ruby

我有一个对象结果包含一个result对象数组以及一些有关数组中对象的缓存统计信息.我希望Results对象能够像数组一样运行.我的第一个切入是添加这样的方法

 def <<(val)
    @result_array << val
 end
Run Code Online (Sandbox Code Playgroud)

这感觉非常像c,我知道Ruby有更好的方法.

我也希望能够做到这一点

 Results.each do |result|   
    result.do_stuff   
 end
Run Code Online (Sandbox Code Playgroud)

但我不确定这种each方法在引擎盖下真正做了什么.

目前我只是通过一个方法返回底层数组并调用它们,这似乎不是最优雅的解决方案.

任何帮助,将不胜感激.

Chu*_*uck 63

对于实现类数组方法的一般情况,是的,您必须自己实现它们.Vava的答案显示了这方面的一个例子.但是,在您给出的情况下,您真正​​想要做的是将处理任务each(以及可能的其他方法)委托给包含的数组,并且可以自动化.

require 'forwardable'

class Results
  include Enumerable
  extend Forwardable
  def_delegators :@result_array, :each, :<<
end
Run Code Online (Sandbox Code Playgroud)

这个类将获得Array的所有Enumerable行为以及Array <<运算符,它将全部通过内部数组.


请注意,当您将代码从Array继承切换到此技巧时,您的<<方法将开始返回不是对象本身,就像真正的数组<<一样 - 这可能会花费您每次使用时声明另一个变量<<.

  • 你需要`扩展Forwardable`,而不是`include`它. (5认同)

vav*_*ava 38

each只需通过数组并使用每个元素调用给定的块,这很简单.由于您在类中使用数组,因此您只需将each方法重定向到数组中的方法,即可快速轻松地读取/维护.

class Result
    include Enumerable

    def initialize
        @results_array = []
    end

    def <<(val)
        @results_array << val
    end

    def each(&block)
        @results_array.each(&block)
    end
end

r = Result.new

r << 1
r << 2

r.each { |v|
   p v
}

#print:
# 1
# 2
Run Code Online (Sandbox Code Playgroud)

请注意,我已混入Enumerable.这将会给你一堆的排列方法,如all?,map等是免费的.

用Ruby来BTW你可以忘记继承.您不需要接口继承,因为duck-typing并不真正关心实际类型,并且您不需要代码继承,因为mixins对于这类事情更好.

  • 是的,它是一个简单的包装器,它是聚合模式,尽可能优先于继承. (2认同)

小智 9

这感觉非常像c,我知道Ruby有更好的方法.

如果你想让一个对象"感觉"像一个数组,那么重写<<是一个好主意而且非常'Ruby'-ish.

但我不确定每种方法在引擎盖下真正做了什么.

Array的每个方法只循环遍历所有元素(我认为使用for循环).如果你想添加自己的每个方法(也非常'Ruby'-ish),你可以这样做:

def each
  0.upto(@result_array.length - 1) do |x|
    yield @result_array[x]
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 为什么不使用`each`迭代`@ result_array`,因为它已经是一个Array的方法? (5认同)

小智 8

你的<<方法非常好,非常像Ruby.

要使类像数组一样,而不直接从Array继承,您可以混合使用Enumerable模块并添加一些方法.

这是一个例子(包括Chuck使用Forwardable的优秀建议):

# You have to require forwardable to use it
require "forwardable"

class MyArray
  include Enumerable
  extend Forwardable

  def initialize
    @values = []
  end

  # Map some of the common array methods to our internal array
  def_delegators :@values, :<<, :[], :[]=, :last

  # I want a custom method "add" available for adding values to our internal array
  def_delegator :@values, :<<, :add

  # You don't need to specify the block variable, yield knows to use a block if passed one
  def each
    # "each" is the base method called by all the iterators so you only have to define it
    @values.each  do |value| 
      # change or manipulate the values in your value array inside this block
      yield value
    end
  end

end

m = MyArray.new

m << "fudge"
m << "icecream"
m.add("cake")

# Notice I didn't create an each_with_index method but since 
# I included Enumerable it knows how and uses the proper data.
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}

puts "What about some nice cabbage?"
m[0] = "cabbage"
puts "m[0] = #{m[0]}"

puts "No! I meant in addition to fudge"
m[0] = "fudge"
m << "cabbage"
puts "m.first = #{m.first}"
puts "m.last = #{m.last}"
Run Code Online (Sandbox Code Playgroud)

哪个输出:

m[0] = fudge
m[1] = icecream
m[2] = cake
What about some nice cabbage?
m[0] = cabbage
No! I meant in addition to fudge
m.first = fudge
m.last = cabbage
Run Code Online (Sandbox Code Playgroud)


Mar*_*une 5

如果您创建一个继承自Array的类Results,您将继承所有功能.

然后,您可以通过重新定义它们来补充需要更改的方法,并且可以为旧功能调用super.

例如:

class Results < Array
  # Additional functionality
  def best
    find {|result| result.is_really_good? }
  end

  # Array functionality that needs change
  def compact
    delete(ininteresting_result)
    super
  end
end
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用内置库forwardable.如果您不能从Array继承,则此功能特别有用,因为您需要从另一个类继承:

require 'forwardable'
class Results
  extend Forwardable
  def_delegator :@result_array, :<<, :each, :concat # etc...

  def best
    @result_array.find {|result| result.is_really_good? }
  end

  # Array functionality that needs change
  def compact
    @result_array.delete(ininteresting_result)
    @result_array.compact
    self
  end
end
Run Code Online (Sandbox Code Playgroud)

在这两种形式中,您可以根据需要使用它:

r = Results.new
r << some_result
r.each do |result|
  # ...
end
r.compact
puts "Best result: #{r.best}"
Run Code Online (Sandbox Code Playgroud)