monkeypatching Set#add时无法调用super

时间:2018-05-16 07:37:27

标签: ruby

我希望修补Set#add接受多个参数,方法与Array#push相同。以下内容:

require 'set'
class Set
  def add(*args)
    args.each { |arg| super arg }
    self
  end
end
s = Set.new
s.add 1,2

不起作用,它引起了:

  

'阻止添加':超级:没有超类方法`add' #(NoMethodError)

我不知道为什么会出现这种情况,因为 我在Set#add来源中看到它有一个标准定义。它是一个如此简单的定义,我可以轻松使用super

require 'set'
class Set
  def add(*args)
    args.each { |arg| @hash[arg] = true }
    self
  end
end
s = Set.new
s.add 1,2

为什么拨打super无效?

2 个答案:

答案 0 :(得分:5)

  

为什么拨打super无效?

这是因为Set#addSet本身上定义,而不在其任何超类上定义。并且你要覆盖这个定义。

答案 1 :(得分:3)

如果您覆盖某个方法,那么您需要更改其定义。致电super将无效;原始方法 not 仍然存在作为转发消息调用的东西。

有几种方法可以解决这个问题。 "经典"方法是根本不重写方法,而是定义自己的Set子类

require 'set'
class MySet < Set
 def add(*args)
    args.each { |arg| super arg }
    self
  end
end
s = MySet.new
s.add 1,2
#=> #<MySet: {1, 2}>

...但这可能并不理想,因为您需要在整个代码库中引用MySet(或任何您称之为的内容),而不是Set

如果您想直接修改Set的行为,那么&#34;老派&#34; ruby方式(不再推荐,如ruby v2.0+)是将原始方法alias改为另一个名称,然后创建自己的版本并调用原始方法: / p>

require 'set'
class Set
  alias_method :original_add, :add
  def add(*args)
    args.each { |arg| original_add(arg) }
    self
  end  
end
s = Set.new
s.add 1,2
#=> #<Set: {1, 2}>

Rails还用于提供帮助方法:alias_method_chain,这使得执行这种常见的解决方法变得更容易。

但很明显,这不是一个很好的解决方案,因为你最终会用original_<foo><foo>_with(out)_<feature>等奇怪的方法来污染课程。值得庆幸的是,Ruby 2.0添加了一种新方法:Module#prepend,它为问题提供了更清晰的解决方案:

require 'set'
module AddWithMultipleArgs
  def add(*args)
    args.each { |arg| super arg }
    self
  end
end

class Set
  prepend AddWithMultipleArgs
end

# Or, you can just call:
# Set.prepend(AddWithMultipleArgs)

s = Set.new
s.add 1,2
#=> #<Set: {1, 2}>

这可以通过在课程中注入新方法 原始版本之前的方法来实现。祖先链:

Set.ancestors
#=> [AddWithMultipleArgs, Set, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]

由于您从未修改过方法的原始版本,因此在Set类本身上,调用super将按预期工作。