这个问题是关于 Ruby 2.2 的。
假设我有一个接受位置和命名参数的方法。
class Parent
def foo(positional, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end
Run Code Online (Sandbox Code Playgroud)
子类既要覆盖一些默认值,又要添加自己的命名参数。我怎么做最好?理想情况下,它不必知道父签名的详细信息,以防父想要添加一些可选的位置参数。我的第一次尝试是这样的。
class Child < Parent
def foo(*args, named1: "child", named3: "child" )
super
end
end
Run Code Online (Sandbox Code Playgroud)
但这会失败,因为未知数named3:被传递给了父级。
Child.new.foo({ this: 23 })
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: this (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
Run Code Online (Sandbox Code Playgroud)
我尝试明确地将参数传递给 super,但这也不起作用。似乎第一个位置参数被视为命名参数。
class Child < Parent
def foo(*args, named1: "child", named3: "child" )
super(*args, named1: "child")
end
end
Child.new.foo({ this: 23 })
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: this (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
Run Code Online (Sandbox Code Playgroud)
我可以让 Child 知道第一个位置参数,它有效......
class Child < Parent
def foo(arg, named1: "child", named3: "child" )
super(arg, named1: "child")
end
end
Child.new.foo({ this: 23 })
Parent.new.foo({ this: 23 })
{:this=>23}
"child"
"parent"
{:this=>23}
"parent"
"parent"
Run Code Online (Sandbox Code Playgroud)
...直到我传入一个命名参数。
Child.new.foo({ this: 23 }, named2: "caller")
Parent.new.foo({ this: 23 }, named2: "caller")
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: named2 (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
Run Code Online (Sandbox Code Playgroud)
我如何进行这项工作并保留命名参数检查的好处?我愿意将位置参数转换为命名参数。
这里的问题是,由于父级对子级的参数一无所知,因此无法知道您传递给它的第一个参数是位置参数,还是打算提供关键字参数父方法。这是因为 Ruby 允许散列作为关键字参数样式参数传递的历史特性。例如:
def some_method(options={})
puts options.inspect
end
some_method(arg1: "Some argument", arg2: "Some other argument")
Run Code Online (Sandbox Code Playgroud)
印刷:
{:arg1=>"Some argument", :arg2=>"Some other argument"}
Run Code Online (Sandbox Code Playgroud)
如果Ruby 不允许使用该语法(这会破坏与现有程序的向后兼容性),您可以使用双 splat 运算符编写这样的子方法:
class Child < Parent
def foo(*args, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[*args, named1: named1, **keyword_args].inspect}"
super(*args, named1: named1, **keyword_args)
end
end
Run Code Online (Sandbox Code Playgroud)
事实上,除了位置参数之外,当您传递关键字参数时,这很有效:
Child.new.foo({ this: 23 }, named2: "caller")
Run Code Online (Sandbox Code Playgroud)
印刷:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
Run Code Online (Sandbox Code Playgroud)
但是,由于当您只传递一个散列时,Ruby 无法区分位置参数和关键字参数之间的区别,因此Child.new.foo({ this: 23 })导致子this: 23进程将其解释为关键字参数,而父方法最终会将转发给它的两个关键字参数解释为一个单一的位置参数(一个散列)代替:
Child.new.foo({this: 23})
Run Code Online (Sandbox Code Playgroud)
印刷:
Passing to parent: [{:named1=>"child", :this=>23}]
{:named1=>"child", :this=>23}
"parent"
"parent"
Run Code Online (Sandbox Code Playgroud)
有几种方法可以解决这个问题,但没有一种方法是完全理想的。
正如您在第三个示例中尝试做的那样,您可以告诉孩子传递的第一个参数将始终是位置参数,其余的将是关键字 args:
class Child < Parent
def foo(arg, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[arg, named1: named1, **keyword_args].inspect}"
super(arg, named1: named1, **keyword_args)
end
end
Child.new.foo({this: 23})
Child.new.foo({this: 23}, named1: "custom")
Run Code Online (Sandbox Code Playgroud)
印刷:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
Passing to parent: [{:this=>23}, {:named1=>"custom"}]
{:this=>23}
"custom"
"parent"
Run Code Online (Sandbox Code Playgroud)
完全切换到使用命名参数。这完全避免了这个问题:
class Parent
def foo(positional:, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end
class Child < Parent
def foo(named1: "child", named3: "child", **args)
super(**args, named1: named1)
end
end
Child.new.foo(positional: {this: 23})
Child.new.foo(positional: {this: 23}, named2: "custom")
Run Code Online (Sandbox Code Playgroud)
印刷:
{:this=>23}
"child"
"parent"
{:this=>23}
"child"
"custom"
Run Code Online (Sandbox Code Playgroud)
编写一些代码以编程方式解决所有问题。
这个解决方案可能非常复杂,并且很大程度上取决于您希望它如何工作,但我们的想法是您将使用Module#instance_method, 并UnboundMethod#parameters读取父级的 foo 方法的签名并相应地将参数传递给它。除非您真的需要这样做,否则我建议您改用其他解决方案之一。
| 归档时间: |
|
| 查看次数: |
1798 次 |
| 最近记录: |