我正在修补数组以添加我自己的方法Array#left_outer_join
。由于我是Ruby的新手,我想做一些漂亮的事情并且有一个方法可以返回一个新的数组和一个将取代我当前的数组的方法。但是,我最后两次编写相同的代码,非bang和bang方法之间的唯一区别是调用map或map!分别
我已经阅读过有关块和元编程的内容,最后得到以下内容:
class Array
def left_outer_join_proc(method, ary, &block)
self.send(method) do |obj1|
ary.each do |obj2|
if yield obj1, obj2
obj2.keys.each do |key|
obj1[key] = obj2[key]
end
break
end
end
obj1
end
end
def left_outer_join(ary, &block)
left_outer_join_proc(:map, ary, &block)
end
def left_outer_join!(ary, &block)
left_outer_join_proc(:map!, ary, &block)
end
end
events.left_outer_join!(users) {|event, user| event['user_id'] == user['user_id'] }
到目前为止这个工作正常,Object.send
是(根据SO)动态调用方法的最佳用法,我有点像这种方法(尽管我的纯粹主义者厌恶污染Array
类用第三种方法)。
现在的问题是:定义非爆炸和爆炸方法并保持干燥的最佳做法是什么?
编辑:这个问题不是关于" bang方法是否意味着破坏性方法?"但是真的关于"如果我写Array#add_two
和Array#add_two!
,怎么能确保我不必定义map{|x| x +2 }
的方法和{{1}的方法}}
我知道我可以使用
map!{|x| x + 2 }
但我要求的是最好的表现,最好的可读性和#34;答案类型(请def add_two!(x)
self = add_two(x)
end
查看Array#map
和Array#map!
以查看“微妙的”性能差异。)
答案 0 :(得分:3)
您还可以将单个项目的代码分解出来,然后在数组方法中稍微冗长一些。这里的left_outer_join_element()
方法本身具有实际意义,即使对于非Array对象也是可重用的。
def left_outer_join(ary, &block)
self.map { |e| left_outer_join_element(e, ary, &block) }
end
def left_outer_join!(ary, &block)
self.map! { |e| left_outer_join_element(e, ary, &block) }
end
protected
def left_outer_join_element(element, ary, &block)
ary.each do |obj|
if yield element, obj
obj.keys.each do |key|
element[key] = obj[key]
end
break
end
end
element
end
答案 1 :(得分:1)
定义非爆炸和爆炸方法的最佳实践是什么
没有适用于所有情况的“最佳实践”。用你的常识。
例如,数组的map!
将就地改变数组。爆炸版本意味着“危险!破坏性方法!”。可能的实施:
def map
# do the mapping
end
def map!
@elements = map
end
如果操作失败,ActiveRecord的 save!
将引发错误。 Bang意味着“会引发错误”。
一般情况下,方法名称中的感叹号应该警告程序员,“注意,这里可能会发生危险的事情”。可能的实施:
def save
save!
true
rescue
false
end
def save!
# do the saving. Raise error if something goes wrong
end
同样,对于方法的爆炸版本,可能存在大量的用例,因此不能有单一的模式/方法。
答案 2 :(得分:1)
在这里看“重复”:
def left_outer_join(ary, &block)
left_outer_join_proc(:map, ary, &block)
end
def left_outer_join!(ary, &block)
left_outer_join_proc(:map!, ary, &block)
end
我会说你在这种情况下已经做了尽可能合理的事。 def
和方法名称是必需的,对proc的调用也是必需的,其中您拥有大部分共享逻辑。尽管字符串编辑方面的差异很小,但差异的放置至关重要。阅读和理解您的代码也相当容易。
为了更进一步,你可以做类似
的事情 ['', '!'].each do |bang_type|
define_method( "left_outer_join#{bang_type}" ) do |ary, &block|
left_outer_join_proc( "map#{bang_type}", ary, &block )
end
end
但是上面的内容是将DRY概念置于极端,因为生成的代码更难以阅读和调试。
答案 3 :(得分:1)
某些对象(例如Array
或String
s)有一个方法允许您将对象的整个内部状态替换为同一类型的另一个对象的状态。此方法通常恰当地命名为replace
。对于具有这种方法的对象,您可以根据replace
和非破坏性方法简单地实现破坏性方法:
def foo
# return new instance
end
def foo!
replace(foo)
nil # or self
end
相反,所有对象都有一个创建具有相同内部状态的副本的方法,称为dup
。您可以使用dup
和破坏性版本
def foo!
# modify internal state
nil # or self
end
def foo
dup.foo!
end
然而,可能存在这样的情况:通过不同地实施破坏性和非破坏性版本可以使其更有效。在这种情况下,可能无法避免一些重复。
请注意
self = add_two(x)
不起作用。它甚至在语法上都没有用。即使 在语法上有效,它也只会分配给名为self
的局部变量,而不会更改特殊关键字self
。