APIのイベントリスナーパターン-同じリスナーを2回追加するとどうなりますか?


8

イベントリスニングインターフェイスを提供するAPIの設計では、リスナーの追加/削除の呼び出しを処理する方法が2つ競合しているようです。

  1. addListenerを複数回呼び出しても、1つのリスナーのみが追加されます(セットに追加するなど)。removeListenerを1回呼び出すだけで削除できます。

  2. addListenerを複数回呼び出すと、毎回リスナーが追加されます(リストに追加するなど)。removeListenerへの複数の呼び出しでバランスを取る必要があります。

私はそれぞれの例を見つけました:(1)-ブラウザーのDOM addEventListener呼び出しはリスナーを1回だけ追加し、同じリスナーを2回追加する要求を静かに無視します(2)-jQuery .on動作はリスナーを複数回追加します

SWTやSwingイベントリスナーなど、他のほとんどのリスナーAPIは(2)を使用しているようです。(1)を選択した場合、同じリスナーを2回追加する要求があったときに、通知なしに失敗するのか、エラーで失敗するのかという問題もあります。

私の実装では、よりクリーンなセットアップ/ティアダウンタイプのインターフェースを提供し、「セットアップ」が意図せずに2回行われ、私が見たほとんどの実装と一致するバグを明らかにするため、私は(2)に固執する傾向があります。

これは私の質問につながります-他の実装に向いている特定のアーキテクチャまたは他の基本的な設計はありますか?(つまり、なぜ他のパターンが存在するのですか?)


1
明確にするために、を検討してくださいaddListener(foo); addListener(foo); addListener(bar);。あなたのケース#1は1と1を追加しますfoobar、それともbar(つまり、リスナーとしてbar上書きするfoo)だけですか?ケース#2の場合、foo2回または1回発砲しますか?
アプシラー2013年

私がよく知っているケース#1の実装は、一般的に参照によって行きます- foo==のbar場合、それは上書きされ、そうでなければ、リスナーとして1つと1つfooを持ちbarます。常に上書きされた場合、それはセットではなく、オブザーバーを表す単一のオブジェクトになります。
Krease

2
(2)は、同じリスナーを2回追加することを禁止しない限り、バグを明らかにしません-そして、(1)に実際の違いはありません。
Doc Brown

選択は、APIユーザーの要件に依存する必要があるため、通常は、そのうちの1人に質問することをお勧めします。ここで設計の決定を行い、ユーザーの1人がAPIを使用して、設計が彼にとってうまくいかないと言った場合、後でその決定を変更する機会がありますか?
Doc Brown

@DocBrown-私が質問した理由である特定のケースでは、変更するオプションがあまりありません。私はどちらか一方のオプションを使用することは大したことではないことを知っているので、問題はより概念的なものです-アーキテクチャ/設計/信頼性に基づいて(つまり、ユーザーの好み以外に)一方から他方のパターンを選択する理由はありますか? ?
Krease

回答:


2

追加/削除の管理で問題が発生しているイベントがある場合は、IDの追加を開始します。

リスナーを追加するとIDが返され、追加したクラスは、追加したリスナーのIDを追跡し、それらを削除する必要がある場合は、その/これらの一意のIDで削除リスナーを呼び出します。

これにより、消費者が行動を制御できるようになり、行動で最小驚きの原則に準拠できるようになります。

つまり、同じものを2回追加すると2回追加され、それぞれに異なるIDが与えられます。IDで削除すると、そのIDに関連付けられているものだけが削除されます。APIを利用する人は、シグニチャを見たときにこの動作を期待します。

YAGNIに違反する追加の追加として、GetIdsがリスナーに渡され、適切な等価性チェックを取得できる場合はそのリスナーに関連付けられたIDのリストが返されますが、これは言語によって異なりますが、参照の等価性です。 、型の等価性、値の等価性など?ここで注意する必要があるのは、そのコンシューマーが削除したり干渉したりしてはならないIDを返す可能性があるためです。したがって、この露出は危険であり、不適切な助言であり、不必要ですが、幸運を感じている場合は、GetIDsを追加することもできます。


追加/削除に問題があるという問題があるわけではありません(質問は私が取り組んでいるプログラムだけでなく、すべてのプログラムに当てはまるので)、このパターンによってAPIが明確に明確になることは明らかですオプション#2を使用する(またはリスナーの代わりにIDで削除する場合のわずかなバリエーション)
Krease

サブスクリプションがサブスクリプション解除に使用できるオブジェクトを返すようにすることは、IMHO 正しいアプローチです。、、またはその他のそのようなインターフェースを備えたオブジェクト指向フレームワークでIDisposableAutocloseable、サブスクリプション解除オブジェクトはそのインターフェースをスレッドセーフな方法で実装する必要があります(常にサブスクライブ解除オブジェクト自体にサブスクライバーを配置し、そのサブスクライブ解除メソッドで無効にすることにより、参照し、サブスクライブリクエストでサブスクリプションリストをスキャンして、無効なサブスクリプションがないか確認します。
スーパーキャット2014

3

まず、リスナーが追加される順序が、関連するイベントがトリガーされたときに呼び出される順序とまったく同じであるアプローチを選択します。(1)を使用する場合は、セットだけでなく、順序付きセットを使用することになります。

第2に、一般的な設計目標を明確にする必要があります。APIは、「クラッシュの早期」戦略、または「エラーを許容する」戦略にもっと従うべきですか これは、APIの使用環境と使用シナリオによって異なります。一般に、(主にデスクトップアプリを開発する)私は「クラッシュ早期」を好みますが、APIの使用をよりスムーズにするために、ある種のエラーを許容する方が良い場合があります。たとえば、埋め込みアプリやサーバーアプリの要件は異なる場合があります。おそらく、これを潜在的なAPIユーザーの1人と話し合っていますか?

「クラッシュアーリー」戦略の場合、(2)を使用しますが、同じリスナーを2回追加することを禁止し、リスナーが再度追加された場合は例外をスローします。また、リストにないリスナーを削除しようとした場合にも例外をスローします。

「エラーを許容する」戦略があなたのケースでより適切であると思うなら、あなたはどちらか

  • リストへの同じリスナーの二重追加を無視する-これはオプション(1)です-または

  • (2)のようにリストに追加して、イベントが発生したときに2回呼び出されるようにします

  • またはそれを追加しますが、イベントトリガーの場合に同じリスナーを2回呼び出さないでください

リスナーの削除はそれに対応する必要があることに注意してください。二重追加を無視する場合は、二重削除も無視する必要があります。同じリスナーを2回追加することを許可する場合は、1つの呼び出し時に2つのリスナーエントリのどちらが削除されるかを明確にする必要がありますremoveListener(foo)。3つの箇条書きの最後は、おそらくこれらの提案の中で最もエラーが発生しにくいアプローチであるため、「エラーを許容する」戦略の場合は、それを採用します。


これまでの私の主な戦略は、「周囲の環境と同様のパターンを使用する」ことでした。つまり、統合している残りのコード/言語で使用される最も一般的なパターンです。「クラッシュアーリー」戦略は多くの潜在的な問題を確実にキャッチしますが、実際にこれが実際に使用されるのを見たことがないので、APIユーザーには驚くかもしれません(考えれば考えるほど、それを考慮します)バグの検出に役立つため、「良い」サプライズ)
Krease

@クリス:「クラッシュ・アーリー」をたくさん見たことがあると思います。たとえば、ほとんどの最近の主流言語では、配列の境界から書き出そうとしたり、文字列を変換不可能な数値に変換しようとしたりすると例外が発生します。
Doc Brown、

私は特にリスナーパターンのコンテキストでそれを言及していました
Krease
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.