web-dev-qa-db-ja.com

変更イベントリスナーを調整するためのデザインパターン

私はJavaScriptでオブザーバーパターンを使用して、さまざまな人気のあるライブラリ(YUIとjQuery)を使用してきました。プロパティ値の変更のセットを監視する必要があることがよくあります(たとえば、2つ以上の特定の値が変更された場合にのみ応答します)。ハンドラーを「サブスクライブ」して、1回だけ呼び出されるようにするエレガントな方法はありますか?デザインに欠けているものや間違っているものはありますか?

5
Kreegr

はい。このため、私はデザインパターンに関する「Gang of Four」の本に戻り、オンラインで調査を行いました。

Mediator-オブジェクトのセットがどのように相互作用するかをカプセル化するオブジェクトを定義します。メディエーターは、オブジェクトが相互に明示的に参照しないようにすることで疎結合を促進し、オブジェクトの相互作用を個別に変更できるようにします。オブジェクトのグループの相互作用を調整します。

状態-内部状態が変化したときに、オブジェクトの動作を変更できるようにします。

JavaScriptメディエーター の例を次に示します。使い方は下にスキップしてください。他の例は、あなたにとってより適切な場合とそうでない場合があります。しかし、それは概念を例示しています。

「オブザーバーパターンを使用して多対多のリスナーとイベントを明示的に設定する代わりに、Mediatorを使用すると、同僚間でイベントをグローバルにブロードキャストできます。」 —ポール・マルコット

ここにいくつかのコードがあります:

//A State Object is simple encapsulation of runtime activity potentially embedded in Mediator.
var interactionState = { //record state, perhaps array, look up function, or even boolean flag.}

//メディエーターはコンポーネントを追加および削除し、イベントをブロードキャストします。

//メディエーターは追加されたオブジェクトをループし、状態を参照して、アクションを実行する必要があるかどうかを判断します。

    for (var c in components) {
        if (typeof components[c]["on" + e] == "function")

          //Add in state comparison
          // if ( interactionState...) //is ready to fire action.
            //{ fire action }

          {
            try {
                //debug("Mediator calling " + e + " on " + c);
                var s = source || components[c];
                components[c]["on" + e].apply(s, a);
            } catch (err) {
                debug(["Mediator error.", e, a, s, err].join(' '));
            }
        }
    }

「適用」動作の実装はおそらく異なり、簡略化される場合があります。

Is there a elegant way to 'subscribe' the handler so that it is only called one time? 

私はそれを「出来事を制限する」とは思わないでしょう。とにかくこれを行うことによる実際のパフォーマンスの向上はおそらくありません。上記の観点は、「2つ以上のオブジェクトがイベントを発生させたので、この特別なタスクを実行できるようにする」という決定メカニズムを導入することです。

メディエーターは決定メカニズムをカプセル化し、状態オブジェクトは実行時の情報の蓄積をカプセル化します。ある時点で、状態をリセットする必要がある場合があることに注意してください。このクリーンアップ動作は、メディエーター内に配置するのが賢明です。

オブザーバーに公開されたイベントをリッスンさせてから、メディエーターを呼び出して状態を確認し、相互作用が関連している場合はそれを判断してから、特別な動作を実行するように指示してイベントをブロードキャストすることができます。

編集:イベントビューもチェックしてください。 これはトピックに関する例外的な記事です。

1
Jack Stone

1つの手法(メディエーターでイベントベースのシステムを使用):

私はメディエーターパターンをかなり活用しています-イベントフローを制御する1つの方法は、モジュールごとに異なるメディエーターオブジェクトをインスタンス化し、特定のメディエーターのみを各モジュールに(またはサンドボックスで)渡すことです-これ各モジュールがその特定のメディアを介してのみ通信できるようにしますそうする必要があると言った場合のみ-そして、この方法では、メッセージをブロードキャストするときに、システム/メディア全体でブロードキャストしませんApplicationDirectorがリッスンしていること。過度に単純化された実装は、次のようになります。

// ApplicationCore / Mediator
var mediator = new Mediator();

var media = {
    authentication: mediator.installTo({}),
    ui: mediator.installTo({}),
    clients: mediator.installTo({})
};

// ... pass each medium to the given module

function broadcast(event, data) {
    for (var medium in media) {
        media[medium].fire(event, data);
    }
}

broadcast('command://start', null);

media.ui.on('action://form/sumbit', function (data) {
    media.authentication.fire('action://form/sumbit', data.email);
    media.clients.fire('action://form/sumbit', data.name.first, data.name.last);
});

// UI Module
sandbox.mediator.on('command://start', function () {
    // ...
    sandbox.mediator.fire('action://form/sumbit', formData);
    // ...
});

// Authentication Module
sandbox.mediator.on('command://start', function () {
    // ...
    sandbox.mediator.on('action://form/sumbit', function () { /* authenticate */ });
    // ...
});

// Clients Module
sandbox.mediator.on('command://start', function () {
    // ...
    sandbox.mediator.on('action://form/sumbit', function () { /* add new client */ });
    // ...
});

これで、Mediatorを使用して、特定の変更(数量など)がシステムの他の部分に影響を与える場合を処理できるようになりました。 whenをより詳細に制御することもできます。指定されたモジュールは、変更をシステムに通知する必要があります。さらに、非常に大規模なアプリケーションを開発している場合は、EventHubを実装するメディエーターを使用することをお勧めします。

この例については多くの奇妙な点があります。

1
Cody

Reactive Extensions フレームワークは、.NETイベントハンドラーのこの問題に対処するために設計されました。 このブログ投稿 の「シーケンスイベント」セクションで説明されているように、RXでイベントのシーケンスに応答する方法を確認することに興味があるかもしれません。基本的に、ユーザーが「ABC」と入力したときにのみ(この順序で)応答したい場合は、次のように言うことができます。

IObservable<Key> keyPress = 
   Observable.FromEvent<KeyEventArgs>(Application.Current.RootVisual, "KeyUp")
      .Select(ev => ev.EventArgs.Key);

Func<Key, IObservable<Key>> pressedIs = 
   key => keyPress.Where(pressedKey => pressedKey == key);

Func<Key, IObservable<Key>> pressedIsNot = 
   key => keyPress.Where(pressedKey => pressedKey != key);

IObservable<Unit> abcPressed =
   from firstKeyPressEvent in pressedIs(Key.A)
   from secondKeyPressEvent in pressedIs(Key.B).Take(1).Until(pressedIsNot(Key.B))
   from thirdKeyPressEvent in pressedIs(Key.C).Take(1).Until(pressedIsNot(Key.C))
   select new Unit();

abcPressed.Subscribe(()=> Debug.WriteLine("ABC was pressed."));

RxJs はjavascriptの実装です。

1

このアイデアがすでに存在しているかどうか、名前が付いているかどうかはわかりません。

あなたはオブジェクトを追加することができます

  • あなたの2つまたは3つの値を観察し、
  • 目的のオブジェクトによって観察され、
  • 条件に達した場合にのみメッセージを送信します。

これは、「条件付きオブザーバープロキシ」のように聞こえます。

0
Sebastian Bauer