5

在运行时的方法中,有没有办法知道该方法是否已super在子类中调用?例如

module SuperDetector
  def via_super?
    # what goes here?
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

Foo.new.bar # => "nothing special"
Fu.new.bar  # => "super!"

我怎么能写via_super?,或者,如果有必要,via_super?(:bar)

4

5 回答 5

4

可能有更好的方法,但总体思路是Object#instance_of?仅限于当前类,而不是层次结构:

module SuperDetector
  def self.included(clazz)
    clazz.send(:define_method, :via_super?) do
      !self.instance_of?(clazz)
    end
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

Foo.new.bar # => "nothing special"
Fu.new.bar  # => "super!"


但是,请注意,这不需要super在孩子中显式。如果孩子没有这样的方法并且使用了父母的方法,via_super?仍然会返回truesuper除了检查堆栈跟踪或代码本身之外,我认为没有办法只捕获这种情况。

于 2016-01-12T06:33:12.240 回答
3

一个优秀的@ndn 方法的附录:

module SuperDetector
  def self.included(clazz)
    clazz.send(:define_method, :via_super?) do
      self.ancestors[1..-1].include?(clazz) &&
        caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
        # or, as by @ndn: caller_locations.take(2).map(&:label).reduce(&:==)
    end unless clazz.instance_methods.include? :via_super?
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"

这里我们Kernel#caller用来确保被调用方法的名称与超类中的名称匹配。如果不是直接后代,这种方法可能需要一些额外的调整(caller(2)应该更改为更复杂的分析),但您可能明白这一点。

UPD感谢@Stefan 对另一个答案的评论,更新unless defined为使其在FooFu include SuperDetector.

UPD2使用祖先来检查 super 而不是直接比较。

于 2016-01-12T06:42:35.267 回答
3

这是一种更简单(几乎是微不足道)的方法,但是您必须同时传递当前类和方法名称:(我还将方法名称从 更改via_super?called_via?

module CallDetector
  def called_via?(klass, sym)
    klass == method(sym).owner
  end
end

示例用法:

class A
  include CallDetector

  def foo
    called_via?(A, :foo) ? 'nothing special' : 'super!'
  end
end

class B < A
  def foo
    super
  end
end

class C < A
end

A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"
于 2016-01-12T10:43:09.570 回答
2

编辑改进,遵循 Stefan 的建议。

module SuperDetector
  def via_super?
    m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
    m0 == m1 and
    (method(m0).owner rescue nil) == (method(m1).owner rescue nil)
  end
end
于 2016-01-12T07:03:43.110 回答
1

我的另一个@mudasobwa和@sawa答案以及递归支持之间的终极组合:

module SuperDetector
  def self.included(clazz)
    unless clazz.instance_methods.include?(:via_super?)
      clazz.send(:define_method, :via_super?) do
        first_caller_location = caller_locations.first
        calling_method = first_caller_location.base_label

        same_origin = ->(other_location) do
          first_caller_location.lineno == other_location.lineno and
            first_caller_location.absolute_path == other_location.absolute_path
        end

        location_changed = false
        same_name_stack = caller_locations.take_while do |location|
          should_take = location.base_label == calling_method and !location_changed
          location_changed = !same_origin.call(location)
          should_take
        end

        self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
      end
    end
  end
end

唯一不起作用的情况(AFAIK)是如果您在基类中有间接递归,但我不知道如何通过解析代码来处理它。

于 2016-01-12T11:16:33.910 回答