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 は文字列を評価することでこれらのことを行いますが、このようにしたいかどうかはまだわかりません。パフォーマンスはまだ重要ではありませんが、重要になるでしょう。したがって、さまざまな可能性とパフォーマンスの問題についてのフィードバックが必要です。

最後に、さらに 2 つの詳細なソリューションを実行しました。最初に、スーパーもセルフもなしでインスタンスを渡します。

  wrap_observer(:[]=) do |instance, index_or_range, *args|
    [instance[index_or_range], instance.send(meth, index_or_range, *args)]
  end

もう 1 つは、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 も使用できます。したがって、モジュールを使用して実行できる 2 つのメソッドを定義する必要があります。

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

したがって、すべてのメソッドを 1 つのループでオーバーライドできます。ハッキングは必要ありません。基本的には、フォールバックおよびテスト参照として使用しますが、他のより特殊なメソッドを実装しています。

于 2012-08-28T13:56:57.830 に答える