在运行时的方法中,有没有办法知道该方法是否已通过子类中的super
调用? E.g。
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)
?
答案 0 :(得分: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?
仍会返回true
。我不认为除了检查堆栈跟踪或代码本身之外,还有一种方法只能捕获super
个案。
答案 1 :(得分: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
进行了更新,以便在Foo
和Fu
{{1}时同时使用}。
UPD2 使用祖先来检查超级而非直接比较。
答案 2 :(得分: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"
答案 3 :(得分: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
答案 4 :(得分:1)
my other,@mudasobwa's和@sawa's答案以及递归支持之间的最终混合:
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)就是你在基类中有间接递归,但我没有想法如何用解析代码来处理它。