想象一下,你经常需要从一些静态代码或方面中的超类中包装一组方法,但是对于每个方法几行结果/不同,例如,围绕数组方法包装更新通知以获得可观察数组。
我的第一个想法是,我希望使用类级方法,如:
class MyArray < Array
extend ObserverHelper # defines :wrap_method
wrap_notify(:[]=) do |index_or_range, *args|
[self[index_or_range], super(index_or_range, *args)]
# much nicer it would be to define
# removed = self[index_or_range]
# added = super(index_or_range, *args)
# but this makes it even more complicated (suggestions welcome!)
end
...
end
到目前为止一切顺利。如何实现:wrap_method现在?我最有希望的方法到目前为止。由于超级不起作用:
require 'observer'
module ObserverHelper
include Observable
def wrap_notify(meth, &block)
define_method meth,->(*args) {
removed, added = instance_exec(*args, &block)
changed
notify_observers(removed, added)
added
}
end
end
错误:
如果直接在数组子类上定义: 超级称为方法之外
如果在模块中定义并包含在数组中: “不支持定义为多个类的单例方法的超级;如果方法具有正确的返回值,则将在1.9.3或更高版本中修复”或“分段错误”)
我搜索了一段时间,但没有找到一个改变闭包绑定的明智的解决方案,但是,也许我想念一些东西,或者你可以解决它?
Rails通过评估字符串来做这些事情,但还不确定如果我想这样做。性能尚不重要,但将会是。因此,我需要有关不同可能性和性能问题的反馈。
最后,我得到了两个更详细的解决方案。首先,传递实例,没有超级也没有自我:
wrap_observer(:[]=) do |instance, index_or_range, *args|
[instance[index_or_range], instance.send(meth, index_or_range, *args)]
end
另一个使用wrap_observer作为instance_method:
def []=(index_or_range, *args)
wrap_observer do
[self[index_or_range], super(index_or_range, *args)]
end
end
我认为更好。
DSL的解决方案可能是在没有包装器的情况下定义方法,而不是迭代添加它的已定义方法。
有没有更多方法可以解决这个高性能并且可以使用我想要的DSL进行维护?
答案 0 :(得分:2)
重新绑定块的唯一方法是使用它定义一个方法,它也允许使用super。因此,您需要定义两个方法 - 可以使用模块完成的任务:
module ObserverHelper
def wrap_notify(meth, options={}, &block)
override = Module.new do
define_method(meth, &block)
end
notifier = Module.new do
define_method meth do |*args|
removed, added = super(*args)
options ||= {}
changed
notify_observers(removed, added)
after
end
end
include override
include notifier
end
end
答案 1 :(得分:0)
此外我想回答我自己的问题,因为这种方法基本上没有用。方法更加多样化(例如已经提到[] =使用数字和范围,但其他方法没有),我找到了一般解决方案:
define_method(meth) do |*args, &block|
old_state = Array.new(self)
r = super(*args,&block)
new_state = Array.new(self)
old_state.delete_if{|e| (i = new_state.index(e)) && new_state.delete_at(i)}
unless new_state.empty? and old_state.empty?
changed
notify_observers(old_state, new_state, meth)
end
r
end
因此,我可以在一个循环中覆盖所有方法,不需要黑客攻击。基本上我将它用作后备和测试参考,而我实现了更专业的方法。