2015-05-07 2 views
2

Скажем, у меня есть родительский класс, инициализатор имеет аргумент со значением по умолчанию:Избегайте повторения именованных аргументов по умолчанию при вызове суперкласса инициализатору в Руби (2.1+)

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

Я хочу создать подкласс, который имеет то же значение по умолчанию для foo. Я могу это сделать, если я повторю заявление:

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 теперь требуется подкласс, и я на самом деле не получаю никакой пользы от значения по умолчанию. не

Я думал, что я мог бы быть в состоянии настроить его 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).

Есть ли какой-либо способ на самом деле оставить аргумент, а не давать ему значение по умолчанию? И/или способ позволить инициализатору принимать произвольные дополнительные имена paraemetrs и передавать их в свой инициализатор суперкласса?


Update: я придумал следующий хак (ручной прокатки мои собственные «параметры по умолчанию», в основном), но я не очень рад.

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 для этого?

ответ

4

В Ruby 2.0+, вы можете используйте оператор double 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 для массивов.

0

Это больше рубин-1,9-иш способом, чем рубин 2,1-иш путь, но я считаю, что это делает то, что вы хотите, в то время как избежать повторения:

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