Ruby块 - 包装和自定义超类方法(使用define_method和blocks)

时间:2012-05-20 15:59:32

标签: ruby binding closures

想象一下,你经常需要从一些静态代码或方面中的超类中包装一组方法,但是对于每个方法几行结果/不同,例如,围绕数组方法包装更新通知以获得可观察数组。

我的第一个想法是,我希望使用类级方法,如:

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进行维护?

2 个答案:

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

因此,我可以在一个循环中覆盖所有方法,不需要黑客攻击。基本上我将它用作后备和测试参考,而我实现了更专业的方法。