如何将“每个”方法添加到Ruby对象(或者我应该扩展数组)?
我有一个对象结果,其中包含一个result
对象数组以及一些有关数组中的对象的caching统计信息。 我希望结果对象能够像数组一样行事。 我的第一个切入点是添加这样的方法
def <<(val) @result_array << val end
这感觉非常像C,我知道Ruby有更好的方法。
我也想能够做到这一点
Results.each do |result| result.do_stuff end
但是我不确定each
方法在底层实际上做了什么。
目前,我只是简单地通过一个方法返回底层数组,并调用它,这似乎并不是最优雅的解决scheme。
任何帮助,将不胜感激。
对于实现类数组方法的一般情况,是的,你必须自己实现它们。 Vava的回答显示了这个的一个例子。 然而,在你给出的情况下,你真正想要做的是委托处理each
(也许一些其他方法)到包含的数组,并且可以自动化的任务。
require 'forwardable' class Results include Enumerable extend Forwardable def_delegators :@result_array, :each, :<< end
这个类将得到所有Array的Enumerable行为以及Array <<
操作符,它将全部通过内部数组。
注意,当你将代码从Arrayinheritance转换到这个技巧时,你的<<
方法将开始不返回对象intself,就像真正的Array的<<
did一样 – 每次使用<<
这会花费你声明另一个variables。
each
只是通过数组,每个元素调用给定的块,这很简单。 因为在类中你也使用了数组,所以你可以将each
方法redirect到数组中的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| pv } #print: # 1 # 2
请注意,我在Enumerable
混合使用。 这会给你一堆像all?
的数组方法all?
, map
等免费。
顺便说一句,你可以忘记inheritance。 你不需要接口inheritance,因为duck-typing并不真正关心实际的types,而且你不需要代码inheritance,因为mixins只是更好的types的东西。
这感觉非常像C,我知道Ruby有更好的方法。
如果你想让一个对象感觉像是一个数组,那么重写<<是一个好主意,而且是'Ruby'-ish。
但是我不确定每种方法在底层实际上做了什么。
Array的每个方法只是遍历所有的元素(使用for循环, 我想 )。 如果你想添加你自己的每个方法(这也是非常'Ruby'-ish),你可以做这样的事情:
def each 0.upto(@result_array.length - 1) do |x| yield @result_array[x] end end
你的<<方法非常好,很像Ruby。
为了使一个类像一个数组一样,不需要直接从Arrayinheritance,你可以混合使用Enumerable模块并添加一些方法。
下面是一个例子(包括Chuck提出的使用Forwardable的优秀build议):
# 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}"
哪些产出:
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
如果你创build一个从Arrayinheritance的类,你将inheritance所有的function。
然后,您可以通过重新定义它们来补充需要更改的方法,并且可以针对旧function调用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
或者,您可以使用内置库可forwardable
。 如果你不能从数组inheritance,这是特别有用的,因为你需要从另一个类inheritance:
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
在这两种forms中,您都可以按照自己的意愿使用它:
r = Results.new r << some_result r.each do |result| # ... end r.compact puts "Best result: #{r.best}"
不知道我是否添加了新的东西,但决定显示一个非常短的代码,我希望我能在答案中find快速显示可用选项。 这里没有@shelvacu所说的统计员。
class Test def initialize @data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38] end # approach 1 def each_y @data.each{ |x| yield(x) } end #approach 2 def each_b(&block) @data.each(&block) end end
让我们检查性能:
require 'benchmark' test = Test.new n=1000*1000*100 Benchmark.bm do |b| b.report { 1000000.times{ test.each_y{|x| @foo=x} } } b.report { 1000000.times{ test.each_b{|x| @foo=x} } } end
结果如下:
user system total real 1.660000 0.000000 1.660000 ( 1.669462) 1.830000 0.000000 1.830000 ( 1.831754)
这意味着yield
比阻止我们已经知道的顺便更快。
更新 :这是国际海事组织最好的方式来创build每个方法,也照顾返回一个枚举
class Test def each if block_given? @data.each{|x| yield(x)} else return @data.each end end end
如果你真的想制作自己的#each方法,并假设你不想转发,如果没有给出块,你应该返回一个枚举器
class MyArrayLikeClass include Enumerable def each(&block) return enum_for(__method__) if block.nil? @arr.each do |ob| block.call(ob) end end end
如果没有给出块,这将返回一个Enumerable对象,允许Enumerable方法链接