一些背景知识:我有一个外部库,它在其中一个方法中使用显式类型检查而不是鸭子类型。类似的东西:
def a_method(value)
case value
when Array then 'an Array'
when Hash then 'a Hash'
when Foo then 'a Foo'
end
end
Foo
在库中定义。我想将另一个对象传递给此方法,该方法应该被视为Foo
。因此,我是Foo
class Bar < Foo
end
效果很好:
bar = Bar.new
a_method(bar)
#=> 'a Foo'
不幸的是,Foo
实现了多个methods
,这将打破Bar
应该工作的方式,包括method_missing
和respond_to?
。例如:
class Foo
def respond_to?(method_name)
false
end
end
由于Foo
是Bar
的超类,因此在调用Foo#respond_to?
时会调用Bar#respond_to?
:
class Bar < Foo
def hello
end
end
bar = Bar.new
bar.respond_to?(:hello) #=> false
bar.method(:respond_to?) #=> #<Method: Bar(Foo)#respond_to?>
我想在这种情况下删除或绕过Foo
的方法(即从Bar
内),以便:
bar.respond_to?(:hello) #=> true
bar.method(:respond_to?) #=> #<Method: Bar(Kernel)#respond_to?>
就好像Foo#respond_to?
不存在一样。
有什么建议吗?
答案 0 :(得分:2)
我不明白这个问题。只需在respond_to?
中实施Bar
,不要致电super
。
class Foo
def respond_to?(method)
puts 'in foo'
false
end
end
class Bar < Foo
def respond_to?(method)
puts 'in bar'
true
end
end
bar = Bar.new
bar.respond_to?(:quux) # => true
# >> in bar
这当然是违反LSP的,但你特意要求它,所以......:)
答案 1 :(得分:2)
Ruby的method lookup似乎是“硬编码的”。我找不到从Ruby中改变它的方法。
基于Sergio Tulentsev关于重新实现这些方法的建议,我想出了一个帮助方法,用它的“超级”方法替换/覆盖每个超类(实例)方法(如果没有,则取消定义):
def self.revert_superclass_methods
superclass.instance_methods(false).each do |method|
super_method = instance_method(method).super_method
if super_method
puts "reverting #{self}##{method} to #{super_method}"
define_method(method, super_method)
else
puts "undefining #{self}##{method}"
undef_method(method)
end
end
end
这与我的目的基本相同。用法示例:
module M
def foo ; 'M#foo' ; end
end
class A
include M
def to_s ; 'A#to_s' ; end
def foo ; 'A#foo' ; end
def bar ; 'A#bar' ; end
end
class B < A
def self.revert_superclass_methods
# ...
end
end
b = B.new
b.to_s #=> "A#to_s"
b.foo #=> "A#foo"
b.bar #=> "A#bar"
B.revert_superclass_methods
# reverting B#to_s to #<UnboundMethod: Object(Kernel)#to_s>
# reverting B#foo to #<UnboundMethod: Object(M)#foo>
# undefining B#bar
b.to_s #=> "#<B:0x007fb389987490>"
b.foo #=> "M#foo"
b.bar #=> undefined method `bar' for #<B:0x007fb389987490> (NoMethodError)
答案 2 :(得分:2)
假设你有这个类结构:
class A
def respond_to?(meth)
"A"
end
def cat
end
def tap
puts "tap in A"
end
end
class B < A
def respond_to?(meth)
"B"
end
end
class C < B
def respond_to?(meth)
"C"
end
end
class D < C
end
我们有:
D.ancestors
#=> [D, C, B, A, Object, Kernel, BasicObject]
此外,您想要:
D.new.respond_to?(:cat)
#=> true
如果我们写:
class D < C
def respond_to?(meth)
method(__method__).super_method.call(meth)
end
end
然后:
D.new.respond_to?(:cat)
#=> C
这并不奇怪,因为它与:
相同class D < C
def respond_to?(meth)
super
end
end
D.new.respond_to?(:cat)
#=> C
现在尝试:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.call(meth)
end
end
然后:
D.new.respond_to?(:cat)
#=> B
所以我们跳过了C
。现在重新定义D#respond_to?
如下:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.super_method.call(meth)
end
end
D.new.respond_to?(:cat)
#=> A
再一次:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.super_method.super_method.call(meth)
end
end
D.new.respond_to?(:cat)
#=> True (invokes Kernel#respond_to?)
因此,您可以执行以下操作:
module JumpOver
def jump_over(over_mod, meth)
m = instance_method(meth)
loop do
m = m.super_method
break if m.owner > over_mod
end
define_method(meth, m)
end
end
class D
extend JumpOver
jump_over(A, :respond_to?)
jump_over(A, :tap)
end
D.methods.include?(:jump)
#=> true
D.instance_methods(false)
#=> [:respond_to?, :tap]
d = D.new
#=> #<D:0x007ff263161b58>
d.respond_to? :cat
#=> true
d.respond_to? :dog
#=> false
d.tap { |o| puts 'hi' }
# 'hi'
#=> #<D:0x007ff263161b58>