2009-06-30 11 views
1

Szenario:Java - Generic Change

Ich habe ein Container-Objekt, das eine bunte Mischung von Objekten alle erben von der MyContainedObject Klasse hält. Verbraucher der Containerklasse haben keinen direkten Zugriff auf enthaltene Objekte, aber ich bin daran interessiert zu wissen, wann sie sich ändern.

Design-Entscheidung:

Was ist der beste Weg für ChangeEvents auf einem bestimmten Klassentyp zu hören? Mein erster Gedanke ist, etwas mit Generika zu tun. Zum Beispiel

private TreeMap<Class, ChangeListener> listeners; 

public <T extends MyContainedObject> addChangeListenerForObjectsOfType(Class<T> className, ChangeListener listener) 
{ 
    listeners.put(className, listener); 
} 

Wenn eine Änderung der Container-Klasse erkannt wird, wäre durch die Liste durchlaufen und nur Zuhörer für diese Klasse Typen registriert benachrichtigen.

Weitere Vorschläge?

Danke.

Antwort

5

ich den Schlüsseltyp auf Ihrem TreeMap gehe davon sollte ein Klasse sein, kein MyContainedObject.

Wenn Sie tatsächlich ChangeEvents für bestimmte Klassentypen überwachen müssen und Elemente auch nach dem Einstellen von Listenern zu Ihrer Sammlung hinzufügen können, erscheint dies ziemlich sinnvoll. Wahrscheinlich möchten Sie mehrere Listener für denselben Typ unterstützen. Sie sollten also entweder eine Multimap-Klasse verwenden (Google Collections hat einige) oder eine Sammlung (wahrscheinlich ein IdentityHashSet) für die Werte in Ihrer Map verwenden.

Sie können dem ChangeListener auch einen Typparameter hinzufügen, damit der Listener das Objekt, auf das das Ereignis gefeuert wurde, für den entsprechenden Typ abrufen kann.

interface ChangeListener<T> { 
    void changed(T obj, /* whatever */); 
} 

Sie müssen einen ungeprüften Guss innerhalb des Containers tun für diese zu arbeiten, aber es sollte sicher sein, solange Ihre Zuhörer Hinzufügen Methode das Richtige tut. zB:

public <T extends MyContainedObject> addChangeListener(Class<T> klass, 
                 ChangeListener<? super T> listener) { 
    ... 
}  

private <T extends MyContainedObject> Set<ChangeListener<? super T>> getChangeListeners(T obj) { 
    Set<ChangeListener<? super T>> result = new IdentityHashSet<ChangeListener<? super T>>(); 
    for (Map.Entry<Class<? extends MyContainedObject>, Set<ChangeListener<?>>> entry : listeners.entrySet()) { 
     if (entry.getKey().isInstance(obj)) { 
      // safe because signature of addChangeListener guarantees type match 
      @SuppressWarnings("unchecked") 
      Set<ChangeListener<? super T>> listeners = 
       (Set<ChangeListener<? super T>>) entry.getValue(); 
      result.addAll(listeners); 
     } 
    } 
    return result; 
} 

Eine kleine nit: Ich würde vermeiden „classname“, wie der Name einer Variablen verwenden, die ein Class-Objekt hält. Ein Klassenname ist ein String, normalerweise das Ergebnis von Class.getName(), etc .. Es ist ein bisschen nervig, aber die Konvention, die ich normalerweise gesehen habe, um die Tatsache zu umgehen, dass "Klasse" ein reserviertes Wort ist, ist falsch es ist entweder "klass" oder "cls".

Wenn Sie die Sammlung nicht aktualisieren müssen, nachdem Sie Listener hinzugefügt haben, dann würde ich mit dem vorgeschlagenen akf gehen, da es einfacher ist.

+0

Danke. Genau das hatte ich vor. Sie haben auch Recht, dass ich meine Klasse und nicht MyContainedObject gemeint habe. – javacavaj

1

Sie können auch einfach Ihren Container Proxy den addChangeListener Aufruf an die enthaltenen Objekte in Frage haben. Dies ermöglicht es ihnen, ihre Listener-Liste beizubehalten und die Aufrufe nach Bedarf ohne die zusätzliche Komplexität einer anderen Ebene in der Listener-Hierarchie abzufeuern.

+1

Dies ist ein guter Vorschlag, aber es funktioniert nur, wenn Sie nie den Satz enthaltenen Objekte nach dem Hinzufügen Hörer ändern, oder wenn Sie mit dem Satz von enthaltenen Objekten und dem Satz von Objekten in Ordnung, die Zuhörer auf sie haben über divergierende Zeit. –

0

Ein Problem mit diesem typspezifischen Benachrichtigungsansatz besteht darin, dass ein Client möglicherweise einen Listener registrieren konnte, der daran interessiert war, wann eine bestimmte Schnittstelle geändert wurde. z.B. addChangeListenerForObjectsOfType(Iterable.class). Dies bedeutet, dass Ihr Benachrichtigungsalgorithmus keine einfache Suche in Ihrer Listener-Map durchführen kann, es sei denn, Sie haben explizit verhindert, dass Listener für Schnittstellen registriert werden, sondern müssen komplexer (und weniger effizient) sein.

Ich würde wahrscheinlich einen anderen Ansatz wählen, als Ihre Implementierung vollständig generisch zu machen. Zum Beispiel, wenn Sie eine Handvoll Top-Level-Unterklassen identifizieren könnten Sie in Ihnen interessieren könnte eine explizite Listener-Schnittstelle zur Verfügung stellen:

public interface Listener { 
    void classAChanged(ChangeEvent e); 
    void classBChanged(ChangeEvent e); 
    void classCChanged(ChangeEvent e); 
} 

ich persönlich diese bevorzugen, da es viel deutlicher für Programmierer ist die Umsetzung Schnittstelle, wodurch der Code lesbarer wird. Offensichtlich ist es unangemessen, wenn Sie Hunderte verschiedener Unterklassen in Ihrer Karte speichern.

Sie könnten diesen einen Schritt weiter gehen und eine generische ChangeEvent<T extends MyObject> -Implementierung bereitstellen, um Downcasting innerhalb Ihrer Listener-Callback-Methoden zu vermeiden.

0

Es gibt ein paar Einschränkungen auf Ihrem vorgeschlagenen Entwurf, der auf Probleme abhängig, welche Arten von Hörern wollen Sie zu einer Zeit

  1. Ein Zuhörer schreiben können nur registrieren Sie sich für eine rohe Art verursachen kann. Wenn ein Listener für viele Typen interessiert ist oder andere Kriterien hat, die er verwenden möchte, um zu entscheiden, an welchen Objekten er interessiert ist, müsste er sich mehrfach registrieren. Außerdem kann der Zuhörer nicht wissen, welche Typen da draußen sind!
  2. Es ist unklar, wie Sie den Fall behandeln, in dem Sie sich für eine Unterklasse von MyContainedObject registrieren, und diese Unterklasse hat Unterklassen.
  3. Änderungen können passieren Instanzen MyContainedObject, dass der Behälter nicht bewusst
  4. ist

ich stattdessen empfehlen, einen Listener für den Behälter aufweist und jeweils auch MyContainedObject Instanz Zuhörer unterstützen:

public interface ContainerListener { 
    void itemAdded(MyContainedObject item); 
    void itemRemoved(MyContainedObject item); 
} 

public interface MyContainedObjectListener { 
    void itemChanged(MyContainedObject item); 
} 

Als Adamski Wenn es eine begrenzte Anzahl von Unterklassen von MyContainedObject gibt, können Sie die Methoden spezifischer machen:

public interface ContainerListener { 
    void circleAdded(Circle item); 
    void squareAdded(Square item); 
    void shapeRemoved(Shape item); 
} 

Alternativ können Sie die spezifischen Methoden in MyContainedObjectListener vornehmen.

Wenn Sie sich entscheiden, dass für einen Typ Registrierung Ihren Bedürfnissen entspricht, dann erwägen, den Zuhörer zu machen generic:

public <T extends MyContainedObject> addChangeListenerFor(Class<T> className, ChangeListener<? super T> listener) 

So oder so, lesen Sie den GoF Abschnitt auf Observer, da es mehrere Realisierungsmöglichkeiten sind, die jeweils mit Vorteilen und Nachteile.