用默认对象填充红宝石数组

dus*_*sch 1 ruby

假设我在ruby中有一个Counter类,定义为

class Counter
    attr_accessor :starting_value
    def initialize(starting_value)
        @starting_value = starting_value
    end

    def tick
        @starting_value = @starting_value + 1
    end
end
Run Code Online (Sandbox Code Playgroud)

我想使用默认参数用该对象填充数组,如下所示: counter_arr = Array.new(5, Counter.new(0))

这几乎是我想要的,除了我现在有一个包含5次相同计数器实例的数组,而不是包含5个新计数器的数组。IE浏览器当我运行代码

counter_arr = Array.new(5, Counter.new(0)) 
counter_arr[0].tick
counter_arr.each do |c|
    puts(c.starting_value)
end
Run Code Online (Sandbox Code Playgroud)

我输出

1
1
1
1
1
Run Code Online (Sandbox Code Playgroud)

代替

1
0
0
0
0
Run Code Online (Sandbox Code Playgroud)

我想知道,用对象的多个新实例初始化数组的“红宝石式”方法是什么?

tad*_*man 5

如果他们不熟悉一种普遍使用对象崇敬的语言,那么当他们学习Ruby时,人们遇到的第一个主要障碍就是它们的工作原理。

数组是对零个或多个其他对象的引用的集合。这些对象不一定是唯一的,并且在某些情况下它们都是相同的。您在这里创建这样的对象:

counters = Array.new(5, Counter.new(0))
Run Code Online (Sandbox Code Playgroud)

这将创建一个单一的Counter对象,并使用它填充数组的所有5个插槽。之所以会这样,是因为方法的参数在调用方法之前先被评估。您可以对此进行测试:

counters.map(&:object_id)
Run Code Online (Sandbox Code Playgroud)

这将返回数组中每个对象的唯一对象ID。它们将是随机值,每个过程都不​​同,但是它们将是相同的。

解决此问题的方法是使用块初始化程序:

counters = Array.new(5) do
  Counter.new(0)
end
Run Code Online (Sandbox Code Playgroud)

那不会插入相同的对象,而是每次评估该块的结果,并且由于它初始化了一个新的Counter对象,因此这些对象将是唯一的。

整理此问题的一种方法是将Counter对象调整为默认值:

class Counter
  def initialize(initial = nil)
    @value = initial.to_i
  end

  def tick
    @value += 0
  end
end
Run Code Online (Sandbox Code Playgroud)

这样做的好处是可以接受任意值,即使那些值不一定是正确的类型。现在Counter.new('2'),该值将被自动转换。这是鸭子打字的基本原理。如果能给你一个数字,那就和数字一样好。