method_missing
在 Ruby 中定义方法时有什么需要注意的地方吗?我想知道继承、异常抛出、性能或其他方面是否存在一些不那么明显的交互。
6 回答
一个有点明显的:respond_to?
如果你重新定义,总是重新定义method_missing
。如果method_missing(:sym)
有效,respond_to?(:sym)
则应始终返回 true。有许多依赖于此的库。
之后:
一个例子:
# Wrap a Foo; don't expose the internal guts.
# Pass any method that starts with 'a' on to the
# Foo.
class FooWrapper
def initialize(foo)
@foo = foo
end
def some_method_that_doesnt_start_with_a
'bar'
end
def a_method_that_does_start_with_a
'baz'
end
def respond_to?(sym, include_private = false)
pass_sym_to_foo?(sym) || super(sym, include_private)
end
def method_missing(sym, *args, &block)
return foo.call(sym, *args, &block) if pass_sym_to_foo?(sym)
super(sym, *args, &block)
end
private
def pass_sym_to_foo?(sym)
sym.to_s =~ /^a/ && @foo.respond_to?(sym)
end
end
class Foo
def argh
'argh'
end
def blech
'blech'
end
end
w = FooWrapper.new(Foo.new)
w.respond_to?(:some_method_that_doesnt_start_with_a)
# => true
w.some_method_that_doesnt_start_with_a
# => 'bar'
w.respond_to?(:a_method_that_does_start_with_a)
# => true
w.a_method_that_does_start_with_a
# => 'baz'
w.respond_to?(:argh)
# => true
w.argh
# => 'argh'
w.respond_to?(:blech)
# => false
w.blech
# NoMethodError
w.respond_to?(:glem!)
# => false
w.glem!
# NoMethodError
w.respond_to?(:apples?)
w.apples?
# NoMethodError
如果您的方法缺失方法只是查找某些方法名称,如果您还没有找到您要查找的内容,请不要忘记调用 super,以便其他方法缺失可以做他们的事情。
如果您可以预期方法名称,最好动态声明它们而不是依赖 method_missing,因为 method_missing 会导致性能损失。例如,假设您想扩展数据库句柄以便能够使用以下语法访问数据库视图:
selected_view_rows = @dbh.viewname( :column => value, ... )
您可以提前确定数据库中的所有视图,然后遍历它们以在 @dbh 上创建“viewname”方法,而不是依赖于数据库句柄上的 method_missing 并将方法名称作为视图的名称发送到数据库.
基于Pistos 的观点:method_missing
至少比调用我尝试过的所有 Ruby 实现的常规方法慢一个数量级。他正确地预见到何时可能避免调用method_missing
.
如果您喜欢冒险,请查看 Ruby 鲜为人知的Delegator类。
James 的回答很棒,但是在现代 ruby(1.9+)中,就像 Marc-André 所说的那样,您想要重新定义respond_to_missing?
,因为它使您可以访问 . 之上的其他方法respond_to?
,例如method(:method_name)
返回方法本身。
例如,定义的以下类:
class UserWrapper
def initialize
@json_user = { first_name: 'Jean', last_name: 'Dupont' }
end
def method_missing(sym, *args, &block)
return @json_user[sym] if @json_user.keys.include?(sym)
super
end
def respond_to_missing?(sym, include_private = false)
@json_user.keys.include?(sym) || super
end
end
结果是:
irb(main):015:0> u = UserWrapper.new
=> #<UserWrapper:0x00007fac7b0d3c28 @json_user={:first_name=>"Jean", :last_name=>"Dupont"}>
irb(main):016:0> u.first_name
=> "Jean"
irb(main):017:0> u.respond_to?(:first_name)
=> true
irb(main):018:0> u.method(:first_name)
=> #<Method: UserWrapper#first_name>
irb(main):019:0> u.foo
NoMethodError (undefined method `foo' for #<UserWrapper:0x00007fac7b0d3c28>)
所以,总是respond_to_missing?
在覆盖时定义method_missing
。
另一个问题:
method_missing
obj.call_method
和之间的行为不同obj.send(:call_method)
。本质上,前者错过了所有私有和未定义的方法,而后者则不会错过私有方法。
因此,method_missing
当有人通过send
.