如何删除或绕过Ruby中的超类方法?

时间:2015-11-27 11:25:28

标签: ruby inheritance

一些背景知识:我有一个外部库,它在其中一个方法中使用显式类型检查而不是鸭子类型。类似的东西:

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_missingrespond_to?。例如:

class Foo
  def respond_to?(method_name)
    false
  end
end

由于FooBar的超类,因此在调用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?不存在一样。

有什么建议吗?

3 个答案:

答案 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>