在 Ruby (2.1+) 中调用超类初始值设定项时避免重复命名参数默认值

Avoid repeating named argument defaults when calling superclass initializer in Ruby (2.1+)

假设我有一个父 class,其初始化器有一个带有默认值的参数:

class Parent
  attr_reader :foo
  def initialize(foo: 123)
    @foo = foo
  end
end

我想创建一个与 foo 具有相同默认值的子 class。如果我重复声明,我可以这样做:

class Child < Parent
  attr_reader :bar
  def initialize(foo: 123, bar: 456)
    super(foo: foo)
    @bar = bar
  end
end

然而,这意味着我必须写两次123。如果我试图通过省略它来避免重复它 --

class Child < Parent
  attr_reader :bar
  def initialize(foo:, bar: 456)
    super(foo: foo)
    @bar = bar
  end
end

-- 这意味着以前可选的、默认的 foo 现在 需要 由 subclass,我实际上没有得到默认值的任何好处。

我想我可以在子class中将它默认为nil --

class Child < Parent
  attr_reader :bar
  def initialize(foo: nil, bar: 456)
    super(foo: foo)
    @bar = bar
  end
end

-- 但没有; Child.new(bar:789).foo 现在是 nil,而我想要的是 123

我也不能完全忽略这个论点 --

class Child < Parent
  attr_reader :bar
  def initialize(bar: 456)
    super(foo: foo)
    @bar = bar
  end
end

-- 因为如果我尝试指定它 (Child.new(foo: 345, bar:789)) 我会得到 unknown keyword: foo (ArgumentError).

有什么方法可以 保留一个参数 ,而不是给它一个默认值? And/or 一种允许初始化器获取任意附加命名参数并将它们传递给其超级class 初始化器的方法?


更新: 我想出了以下技巧(基本上是我自己动手 'default parameters'),但我对此不是很满意。

class Parent
  attr_reader :foo
  def initialize(foo: nil)
    @foo = foo || 123 # faking 'default'-ness
  end
end

class Child < Parent
  attr_reader :bar
  def initialize(foo: nil, bar: 456)
    super(foo: foo)
    @bar = bar
  end
end

肯定还有更多 Ruby-ish 方法可以做到这一点?

这更像是 Ruby-1.9-ish 方式而不是 Ruby-2.1-ish 方式,但我相信它可以满足您的需求,同时避免重复:

class Parent
  attr_reader :foo
  def initialize(opts = {})
    @foo = opts[:foo] || 123
  end
end

class Child < Parent
  attr_reader :bar
  def initialize(opts = {})
    super(opts)
    @bar = opts[:bar] || 456
  end
end

puts Parent.new.foo # => 123
puts Parent.new(foo: 1).foo # => 1
puts Parent.new(bar: 2).foo # => 123
puts Parent.new(foo: 1, bar: 2).foo # => 1

在Ruby 2.0+中,您可以使用双splat运算符。

def initialize(bar: 456, **args)
  super(**args)
  @bar = bar
end

一个例子:

[1] pry(main)> class Parent
[1] pry(main)*   def initialize(a: 456)
[1] pry(main)*     @a = a
[1] pry(main)*   end  
[1] pry(main)* end  
=> :initialize
[2] pry(main)> class Child < Parent
[2] pry(main)*   def initialize(b: 789, **args)
[2] pry(main)*     super(**args)
[2] pry(main)*     @b = b
[2] pry(main)*   end  
[2] pry(main)* end  
=> :initialize
[3] pry(main)> ch = Child.new(b: 3)
=> #<Child:0x007fc00513b128 @a=456, @b=3>
[4] pry(main)> ch = Child.new(b: 3, a: 6829)
=> #<Child:0x007fc00524a550 @a=6829, @b=3>

双 splat 运算符类似于单 splat 运算符,但它不是将所有额外的 args 捕获到数组中,而是将它们捕获到哈希中。然后,当用作 super 的参数时,double splat 将散列扁平化为命名参数,有点像 single splat 对数组的作用。