Beim Entwerfen einer API, die eine Schnittstelle zum Abhören von Ereignissen bereitstellt, gibt es anscheinend zwei widersprüchliche Methoden zum Behandeln von Aufrufen zum Hinzufügen / Entfernen von Listenern:
Bei mehreren Aufrufen von addListener wird nur ein einziger Listener hinzugefügt (wie beim Hinzufügen zu einem Set). kann mit einem einzigen Aufruf von removeListener entfernt werden.
Bei mehreren Aufrufen von addListener wird jedes Mal ein Listener hinzugefügt (z. B. zum Hinzufügen zu einer Liste). muss durch mehrere Aufrufe von removeListener ausgeglichen werden.
Ich habe jeweils ein Beispiel gefunden: (1) - Der Aufruf von DOM addEventListener in Browsern fügt Listener nur einmal hinzu, ignoriert stillschweigend Anfragen, denselben Listener ein zweites Mal hinzuzufügen, und (2) - jQuery- .on
Verhalten fügt Listener mehrmals hinzu .
Die meisten anderen Listener-APIs scheinen (2) zu verwenden, z. B. SWT- und Swing-Ereignis-Listener. Wenn (1) ausgewählt ist, stellt sich auch die Frage, ob es stillschweigend oder mit einem Fehler fehlschlagen soll, wenn derselbe Listener zweimal hinzugefügt werden muss.
In meinen Implementierungen bleibe ich eher bei (2), da es eine übersichtlichere Setup- / Teardown-Schnittstelle bietet und Fehler aufdeckt, bei denen das Setup unbeabsichtigt zweimal durchgeführt wird und mit den meisten Implementierungen übereinstimmt, die ich gesehen habe.
Dies führt mich zu meiner Frage: Gibt es eine bestimmte Architektur oder ein anderes zugrunde liegendes Design, das sich besser für die andere Implementierung eignet? (dh: warum existiert das andere Muster?)
quelle
addListener(foo); addListener(foo); addListener(bar);
. Fügt Ihr Fall Nr. 1 einsfoo
und eins hinzubar
oder nurbar
(dhbar
überschreibtfoo
als Hörer)? Würde Fall 2foo
zweimal oder einmal feuern?foo
==bar
, dann würde es überschreiben, andernfalls würde es einsfoo
und einsbar
als Zuhörer haben. Wenn es immer überschrieben würde, wäre es keine Menge, sondern ein einzelnes Objekt, das einen Beobachter darstellt.Antworten:
Wenn Sie einige Ereignisse haben, bei denen Probleme beim Verwalten des Hinzufügens / Entfernens auftreten, würde ich mit dem Hinzufügen von IDs beginnen.
Hinzufügen eines Listeners gibt eine ID zurück, die Klasse, die sie hinzugefügt hat, verfolgt die IDs für die hinzugefügten Listener, und wenn sie entfernt werden müssen, ruft sie remove listener mit dieser / diesen eindeutigen IDs auf.
Dies gibt den Verbrauchern die Kontrolle, damit sie das Prinzip des geringsten Erstaunens im Verhalten einhalten können.
Dies bedeutet, dass dasselbe zweimal hinzugefügt wird, zweimal hinzugefügt wird, für jedes eine andere ID angegeben wird und durch Entfernen nach ID nur die mit dieser ID verknüpfte ID entfernt wird. Jeder, der die API verwendet, würde dieses Verhalten erwarten, wenn er die Sigs sieht.
Ein weiterer Zusatz, der gegen YAGNI verstößt, wäre ein GetIds, bei dem Sie ihm einen Listener übergeben und eine Liste der mit diesem Listener verknüpften IDs zurückgeben, wenn er in der Lage ist, geeignete Gleichheitsprüfungen zu erhalten, obwohl dies von Ihrer Sprache abhängt: Ist es Referenzgleichheit? , Typengleichheit, Wertgleichheit usw.? Sie müssen hier vorsichtig sein, da Sie möglicherweise IDs zurückgeben, die der Verbraucher nicht entfernen oder einmischen sollte. Daher wird diese Exposition gefährlich und ist schlecht beraten und sollte unnötig sein. GetIDs sind jedoch eine mögliche Ergänzung, wenn Sie Glück haben.
quelle
IDisposable
,Autocloseable
oder andere solche Schnittstelle sollten das Abmelde Objekt diese Schnittstelle in Thread-sicheren Art und Weise implementieren (immer möglich - wenn nichts anderes durch die Teilnehmer innerhalb des Abmelde - Objekts platzieren selbst und mit seinen Abmeldeverfahren entkräften, Referenz und Abonnementanforderungen durchsuchen gelegentlich die Abonnementliste nach toten Abonnements.Zunächst würde ich einen Ansatz wählen, bei dem die Reihenfolge, in der die Listener hinzugefügt werden, genau der Reihenfolge entspricht, in der sie aufgerufen werden, wenn die zugehörigen Ereignisse ausgelöst werden. Wenn Sie sich für (1) entscheiden, bedeutet dies, dass Sie ein geordnetes Set verwenden, nicht nur ein Set.
Zweitens sollten Sie ein allgemeines Entwurfsziel klarstellen: Soll Ihre API eher einer "Absturz-Früh" -Strategie oder einer "Fehlerverzeihungs" -Strategie folgen? Dies hängt von der Nutzungsumgebung und den Nutzungsszenarien Ihrer API ab. Im Allgemeinen (bei der Entwicklung von hauptsächlich Desktop-Apps) bevorzuge ich "früh abstürzen", aber manchmal ist es besser, Fehler zu tolerieren, um die Verwendung einer API reibungsloser zu gestalten. Die Anforderungen, beispielsweise in eingebettete Apps oder Server-Apps, können unterschiedlich sein. Vielleicht diskutieren Sie dies mit einem Ihrer potenziellen API-Benutzer?
Verwenden Sie für eine "Crash Early" -Strategie (2), aber verbieten Sie das zweimalige Hinzufügen desselben Listeners. Wenn ein Listener erneut hinzugefügt wird, wird eine Ausnahme ausgelöst. Wirf auch eine Ausnahme aus, wenn versucht wird, einen Listener zu entfernen, der nicht in der Liste enthalten ist.
Wenn Sie der Meinung sind, dass eine Strategie zur Fehlerverzeihung in Ihrem Fall angemessener ist, können Sie dies auch tun
Ignorieren Sie das doppelte Hinzufügen desselben Listeners zur Liste - dies ist Option (1) - oder
Hängen Sie es wie in (2) an die Liste an, damit es beim Auslösen der Ereignisse zweimal aufgerufen wird
oder Sie hängen es an, rufen aber im Falle einer Ereignisauslösung nicht zweimal denselben Listener an
Beachten Sie, dass das Entfernen des Listeners dem entsprechen sollte. Wenn Sie das doppelte Hinzufügen ignorieren, sollten Sie auch das doppelte Entfernen ignorieren. Wenn Sie zulassen, dass derselbe Listener zweimal hinzugefügt wird, sollte klar sein, welcher der beiden Listener-Einträge entfernt wird, wenn einer anruft
removeListener(foo)
. Die letzte der drei Aufzählungszeichen ist höchstwahrscheinlich der am wenigsten fehleranfällige Ansatz unter diesen Vorschlägen. Im Falle einer Strategie zur "Fehlerverzeihung" würde ich mich dem anschließen.quelle