重新定义Ruby方法的奇怪行为

时间:2014-02-28 20:03:51

标签: ruby

为什么是程序的输出:

require 'set'

class Set
  alias :old_add :add
  def add(arg)
    arg = 5
    old_add arg
  end
end

s = Set.new ([1,2])
s.add(3)

puts s.inspect

#<Set: {5}>

而不是

#<Set: {1,2,5}>

方法add被重新定义为使用参数5运行。

2 个答案:

答案 0 :(得分:3)

查看Set::new的来源:

# File set.rb, line 80
def initialize(enum = nil, &block) # :yields: o
  @hash ||= Hash.new

  enum.nil? and return

  if block
    do_with_enum(enum) { |o| add(block[o]) }
  else
    # you did not supply any block, when you called `new`
    # thus else part will be executed here
    merge(enum)
  end
end

似乎Set.new在内部调用方法#add。在OP的示例中,blocknil,因此调用了#merge

# File set.rb, line 351
def merge(enum)
  if enum.instance_of?(self.class)
    @hash.update(enum.instance_variable_get(:@hash))
  else
    # in your case this else part will be executed.
    do_with_enum(enum) { |o| add(o) }
  end

  self
end

因此addenum)的每个元素都会调用[1,2]。在这里,您覆盖了原始的#add方法,并且在该方法中,您使用参数#add调用旧的5方法。

Set实现了一组无序值,没有重复。因此,即使您添加5两次,您也只能获得一个5。这是您没有获得#<Set: {1,2}>,而是#<Set: {5}>的原因。如下所示,当您致电Set.new时,对象#<Set: {5}>就像我上面解释的那样创建:

require 'set'

class Set
  alias :old_add :add
  def add(arg)
    arg = 5
    old_add arg
  end
end

s = Set.new ([1,2])
s # => #<Set: {5}>

当您致电s.add(3)时,系统会调用您重写的add方法,并再次将5传递给旧的add方法。正如我之前所说,Set不包含重复值,因此该对象仍将与之前的#<Set: {5}>相同。

答案 1 :(得分:0)

当您使用给定成员实例化新集时,将使用add方法添加成员。这可以通过在创建它之后调查该集来显示:

Set.new([1,2])
# => #<Set: {5}>

因此,当你执行s.add(3)时,你基本上再次添加5,它已经被添加了两次,因此该方法实际上并没有改变集合。