写两个类似的方法 - 一个与bang,一个没有 - 同时尊重DRY概念

时间:2013-10-15 09:13:07

标签: ruby metaprogramming

我正在修补数组以添加我自己的方法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_twoArray#add_two!,怎么能确保我不必定义map{|x| x +2 }的方法和{{1}的方法}}

我知道我可以使用

map!{|x| x + 2 }

但我要求的是最好的表现,最好的可读性和#34;答案类型(请def add_two!(x) self = add_two(x) end 查看Array#mapArray#map!以查看“微妙的”性能差异。)

4 个答案:

答案 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)

某些对象(例如ArrayString 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