3

想象一下,您经常需要将一组来自超类的方法包装在一些静态代码或方面,但每个方法的结果/不同的行数很少,例如,将更新通知包装在数组方法周围以获得可观察的数组。

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

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 通过评估字符串来做这些事情,还不确定我是否想这样。性能尚不重要,但会很重要。因此,我需要有关不同可能性和性能问题的反馈。

最后,我运行了两个更详细的解决方案。首先,传递实例,没有 super 也没有 self:

  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 来解决这个高性能和可维护的问题?

4

2 回答 2

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
于 2012-08-14T14:56:00.247 回答
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

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

于 2012-08-28T13:56:57.830 回答