【发布时间】:2019-12-23 22:44:40
【问题描述】:
有没有办法监视对象,以便在调用该对象的特定方法之前和/或之后运行块或 Lamba?例如,像这样:
watch(lionel, :say_you, :before) do
puts '[before say_you]'
end
lionel.say_you()
# outputs [before say_you]
我的要求的一个重要部分是我根本不想修补对象。对象不应有任何更改。我只想看它,而不是改变它(海森堡会很自豪)。
我已经编写了一个模块,可以实现我所描述的功能。不幸的是,它有一些不好的副作用:它会减慢系统速度,并且永远不会清理它的对象 ID 散列。所以我不会在生产中使用它,但它展示了观察对象而不用猴子修补它的概念。
# watcher module
# (Adrian is one of The Watchmen)
module Adrian
@registry = {}
EVENT_IDS = {:before => :call, :after => :return}
# watch
def self.watch(obj, method, event_id, &handler)
# get event type
event = EVENT_IDS[event_id]
event or raise 'unknown-event: unknown event param'
# get object id
obj_id = obj.object_id
# store handler
@registry[obj_id] ||= {}
@registry[obj_id][method] ||= {}
@registry[obj_id][method][event] ||= []
@registry[obj_id][method][event].push(handler)
end
# trace
TracePoint.trace(:call, :return) do |tp|
# get watched object or return
if handlers = @registry[tp.binding.receiver.object_id]
handle tp, handlers
end
end
# handle
def self.handle(tp, handlers)
# $tm.hrm
# if this method is watched
callee = handlers[tp.callee_id]
callee or return
# get blocks
blocks = callee[tp.event]
blocks or return
# loop through series
blocks.each do |block|
block.call
end
end
end
# Lionel class
class Lionel
def say_you
puts 'you'
end
def say_me
puts 'me'
end
end
# instance
lionel = Lionel.new()
# before
Adrian.watch(lionel, :say_you, :before) do
puts '[before say_you]'
end
# after
Adrian.watch(lionel, :say_me, :after) do
puts '[after say_me]'
end
# call method
lionel.say_you
lionel.say_me
输出:
[before say_you]
you
me
[after say_me]
【问题讨论】:
-
为什么不给对象打补丁呢?这就是这种类型的仪器通常是如何完成的。这个,或者装饰器(但是装饰一个对象会生成一个新的对象,这可能不是你想要的)。
-
-
@tadman:
lionel是一个本地变量。 -
@SergioTulentsev 哦,在那种情况下,这真的不切实际。如果没有一些黑魔法,你就无法包装对变量的引用。
-
@tadman:我会使用一些 MP 魔法,是的。但 Mike 设计了一种基于跟踪点的解决方案,即无补丁。有自己的缺点。
标签: ruby