Mehrfaches Implementieren eines generischen Interface

I.Haffner

Mitglied
Hallo liebes Java-Forum,
ich würde gerne eine Observer-Observable Struktur anlegen.

Es gibt eine abstrakte Klasse Observable und ein Interface Observer<T>. Mal ein Codebeispiel:

Code:
public abstract class Observable {
    
    private Set<Observer> observers;
    
    public Observable() {
        observers = new HashSet<Observer>();
    }
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void notifyObservers() {
        for(Observer o : observers) {
            o.update(this);
        }
    }
    
}

public interface Observer<T> {
    
    void update(T o);
    
}

Nun möchte ich, dass alle Klassen, die Observiert werden können, Observable erweitern, und alle Klassen, die observieren können, Observer erweitern. Nun möchte ich jedoch, dass eine Observer-Klasse verschiedene Observable-Klassen observieren kann. Hier wieder ein Code-Beispiel:

Code:
public class MyObserver imiplements Observer<ClassA>, Observer<ClassB> {
    
    public void update(ClassA o) {
        
    }

    public void update(ClassB o) {
        
    }

}

Allerdings ist die mehrfache Implementierung eines Interface nicht zulässig (auch mit generischem Typ).
Nun würde ich gerne wissen, wie ich mein Problem effizient lösen kann.

Ein eigenes Interface zu entwerfen dass dann ein bestehendes erweitert um so eine weitere update-Methode hinzuzufügen bietet sich nicht an! Die Anzahl der zu observierenden Klassen ist einfach zu groß und unterschiedlich.

Ich warte auf konstruktive Beiträge 🙂

I.Haffner
 
Sooo viele Möglichkeiten gibt's da nicht ... (wobei das davon abhängt, wie weit man ausholt).

Aber erstmal allgemein: Ich habe die Observer/Observable aus java.util noch nie wirklich in der "Praxis" eingesetzt gesehen (Ich hab' manchmal das Gefühl die sind nur "der Vollständigkeit halber" dabei...). Es ist nicht unüblich, sich für seine eigenen Klassen auch eigene Interfaces zu definieren, die die Observierbarkeit regeln. Das hat viele Vorteile, abgesehen von Typsicherheit und sprechenden Namen schon den, dass man die Granulaität der Benachrichtigungen selbst wählen kann.

Natürlich kann es bei vielen Klassen aufwändig werden. Das ist eigentlich erstmal kein Grund, es nicht zu tun. Aber vielleicht aus einem ähnlichen (oder sogar dem gleichen) Grund wie du hatte ich damals gefragt: http://www.java-forum.org/allgemein...rwendet-man-propertychangedevents-eigene.html - Eine eindeutige Antwort oder ein Silver Bullet gab es (leider, mal wieder) nicht. (Wobei vielleicht schon PropertyChangeListeners das Problem in deinem Fall "abschwächen" könnten, zumindest im Vergleich zu observer/observable).

Je nachdem, worum es geht, könnte man sich natürlich irgendwelche Mischformen überlegen: MUSS MyObserver wirklich beide Interfaces implementieren? Man könnte zwei Klassen dafür machen, oder ggf. sowas wie
Java:
class MyObserver {
    private final Observer<A> observerA = new Observer<A>()
    {
        @Override
        public void update(A o)
        {
            daUpdateA(o);
        }
    };

    private void doUpdateA(A o)
    {
    }

    public Observer<A> getObserverA() { return observerA; }
...
    public Observer<B> getObserverB() { ... }
}
wo man sich den passenden Observer abholt. Wenn es wirklich SEHR viele und SEHR ähnliche Klassen/Strukturen sind, könnte man sich vielleicht auch was noch allgemeineres ausdenken, irgendwas wo eine [c]Map<Class<?>, List<Observer<?>>[/c] drin vorkommt oder so, aber ... das ist Spekulation.

Es gibt ... anscheinend DOCH viele Möglichkeiten 😳 😉
 
Als erstes mal vielen Dank für die Antwort. Ja die Referenzimplementierungen von Observer/Observable wollte ich auch nicht verwenden. Der Sinn des generischen Typs ist der, dass ich für ein Spiel, dass ich entwickeln muss, eine GUI entwerfe. Dabei sollen einzelne Elemente der GUI (z.B. JPanel etc.) die Schnittstelle Observer<T> implementieren. Der generische Typ dient dann dazu um zu spezifizieren, welche Klassen der Observer überwacht. So kann z.B. ein GUI-Element sowohl ClassA als auch ClassB observieren. Um aber dann in update() unnötige und aufwenige casts zu vermeiden würde ich hier sehr gerne über den dynamischen Typ die Methode überladen. Das würde mir eine ganze Menge Arbeit ersparen... und der Code ist viel dynamischer. Was im bisherigen Entwurf schonmal gut klappt, ist, dass ein Observable von verschiedenen Observern überwacht werden kann (nicht grad spektakulär 😀)

Ich hoffe ich oder ihr finden noch eine elegante Lösung für das Problem.
Ich überlege ob sich hier vllt. das Visitor-Pattern anwenden lässt um an den dynamischenTyp zu gelangen.
Ich werd mal sehen wie weit ich komme.

I.Haffner
 
Nur ob ich es richtig verstanden habe:

Java:
class ClassA extends Observable{}

class ClassB extends Observable{}

class MyObserver implements Observer{

   public void update(Observable o){
      //Das willst du vermeiden
      if(o instanceOf ClassA){...}
      if(o instanceOf ClassB){...}
   }
}

Ich sehe 3 Möglichkeiten:

1. Du machst pro Observable eine eigene Observer-Klasse.

2. Du versuchst ein gemeinsames Interface für Klasse ClassA und ClassB zu definieren, so dass der Observer gegen dieses Interface arbeitet und gar nicht wissen muss mit welcher konkreten Implementierung er es zu tun hat.

3. Du wirfst einen Blick auf den EventBus aus dem guava-Projekt (EventBusExplained - guava-libraries - Guava's event bus utility, explained. - Guava: Google Core Libraries for Java 1.6+ - Google Project Hosting)
 
Hey ButAlive,
du hast mich sehr richtig verstanden 🙂

Methode 1 kommt schonmal nicht in Frage. Die Anzahl der Möglichkeiten, wie Observer kombiniert werden können, ist viel zu hoch. Für jede Kombination eine eigene Klasse anzulegen ist zu aufwendig. (Da caste ich ja lieber 😀)

Methode 2 würde mir zwar etwas arbeit ersparen, allerdings wird das Projekt dann unglaublich festgefahren. Was ist wenn auf einmal ein ganz neuer Observer hinzukommt? Jede Klasse ändern? Nein, danke 😛

Aus Methode 3 konnte ich leider keinen Nutzen ziehen. Wie dort auch zu lesen ist, ist Mehrfachimplementierung eines generischen Interface aufgrung von type erasure leider nicht möglich...

Trotzdem schonmal vielen Dank.

Ich versuche derzeit ein effizientes work-around zu finden... leichter gesagt als getan 😛
 
So leute, ich habe nun ein schönes Workaround für mein Problem gefunden 🙂
Natürlich ist es nicht ganz das, was ich mir erhofft habe. Aber ich spare mir damit einiges an Schreibarbeit.

Die Lösung bestand darin, das Interface des Observers als Kompositum zu implementieren. Es gibt also einzelne Observer zu jeder Klasse, und ein Kompositum, dem beliebige Observer hinzugefügt werden können. Um nun innerhalb des Kompositums zugriff auf den dynamischen Typ des observable-objects zu bekommen, muss jede Klasse die AbstractObservable erweitert eine methode
Code:
void accept(Observer observer)
imlementieren.
Das Kompositum über die Observer leitet dann den Aufruf von
Code:
void update(T o)
an seine Komponenten weiter. Dabei kann es durchaus passieren, dass eine ClassCastException geworfen wird (zB überwacht ein Kompositum mit einem ObserverA und einem ObserverB nur ein ObservableA. Dann kann ObservableA natürlich nicht zu ObservableB gecastet werden)

Das alles funktioniert sehr gut. Leider etwas unschön wg. der Exception.


Hier nun mal das Codebeispiel:

Code:
public interface Observable {
    
    void addObserver(Observer observer);
    void notifyObservers();
    Collection<Observer> getObservers();
    void accept(Observer observer);
    
}

public abstract class ObservableAbstract implements Observable {
    
    private Set<Observer> observers;
    
    public ObservableAbstract() {
        observers = new HashSet<Observer>();
    }

    @Override
    public void addObserver(Observer observer) {
        if(observer == null) {
            throw new NullPointerException();
        }
        observers.add(observer);
    }
    
    @Override
    public void notifyObservers() {
        for(Observer observer : getObservers()) {
            observer.update(this);
        }
    }
    
    @Override
    public Collection<Observer> getObservers() {
        return Collections.unmodifiableCollection(observers);
    }

}

public class ObservableA extends ObservableAbstract {

    @Override
    public void accept(Observer observer) {
        observer.update(this);
    }
    
    @Override
    public String toString() {
        return "ObservableA";
    }

}

public class ObservableB extends ObservableAbstract {

    @Override
    public void accept(Observer observer) {
        observer.update(this);
    }
    
    @Override
    public String toString() {
        return "ObservableB";
    }
    
}

public interface Observer<T extends Observable> {
    
    /**
     *
     * @param o
     */
    void update(T o);
    
}

public class ObserverA implements Observer<ObservableA> {

    /**
     *
     * @param o
     */
    @Override
    public void update(ObservableA o) {
        System.out.println("A");
    }
    
    @Override
    public String toString() {
        return "ObserverA";
    }

}

public class ObserverB implements Observer<ObservableB> {

    /**
     *
     * @param o
     */
    @Override
    public void update(ObservableB o) {
        System.out.println("B");
    }
    
    @Override
    public String toString() {
        return "ObserverB";
    }

}

public class ObserverGroup implements Observer<Observable> {
    
    private Set<Observer> observers;
    
    public ObserverGroup() {
        observers = new HashSet<Observer>();
    }
    
    /**
     *
     * @param observer
     */
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     *
     * @param o
     */
    @Override
    public void update(Observable o) {
        for(Observer observer : observers) {
            try{
                o.accept(observer);
            } catch(ClassCastException ex) {
                /*
                 * this might happen if an observable invoked
                 * the notifyObservers-method and an observer
                 * of another type should be updated
                 */
            }
        }
    }
    
}

Und hier nun mal ein Beispiel beim Ausführen:

Code:
public class GenerischesInterface {
    
    Observable observableA, observableB, observableC;
    Observer observerA, observerB, observerAB;
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        GenerischesInterface generischesInterface = new GenerischesInterface();
    }
    
    public GenerischesInterface() {
        observableA = new ObservableA();
        observableB = new ObservableB();
        observableC = new ObservableC();
        
        observerA = new ObserverA();
        observerB = new ObserverB();
        
        ObserverGroup observerGroup = new ObserverGroup();
        observerGroup.addObserver(observerA);
        observerGroup.addObserver(observerB);
        observerAB = observerGroup;
        
        observableA.addObserver(observerA);     // A
        observableA.addObserver(observerAB);    // A
        observableB.addObserver(observerB);     // B
        observableB.addObserver(observerAB);    // B
        observableC.addObserver(observerAB);    // 
        
        observableA.notifyObservers();
        observableB.notifyObservers();
        observableC.notifyObservers();
    }
}

Und der Output ist:
Code:
AABB
 
Aus Methode 3 konnte ich leider keinen Nutzen ziehen. Wie dort auch zu lesen ist, ist Mehrfachimplementierung eines generischen Interface aufgrung von type erasure leider nicht möglich...

Da hast du mich falsch verstanden. Dein eigentliches Problem ist ja, dass du unterschiedliche Klassen hast, die du beobachten willst, da du aber nur einen "Beobachter" haben willst, kommst du mit dem Observer-Pattern nicht drum rum, zur Laufzeit den Typen zu prüfen und zu casten. Mit deiner jetztigen Lösung hast du jetzt auch pro Klasse einen Observer.

Es gibt auch noch andere Möglichkeiten dies zu lösen, als das klassische Observer-Pattern, zum Beispiel mit Hilfe des EventBus von Guava.

Hier zwei Klassen, die bei einer Zustandsänderung dies dem Eventbus mitteilen. Dies würde deinen Observables entsprechen.

Java:
package eventbusexample;

import com.google.common.eventbus.EventBus;

public class ClassA {

	private final EventBus eventbus;
	
	private int i=0;
	
	public ClassA(EventBus eventbus) {
		this.eventbus = eventbus;
	}
	
	public void register(Receiver r){
		eventbus.register(r);
	}

	public void doSomething(){
		i++;		
		eventbus.post(this);
	}

	public int getI() {
		return i;
	}
}

package eventbusexample;

import com.google.common.eventbus.EventBus;

public class ClassB {

	private final EventBus eventbus;
	
	private int j=0;
	
	
	public ClassB(EventBus eventbus) {
		this.eventbus = eventbus;
	}
	
	public void register(Receiver r){
		eventbus.register(r);
	}


	public void doSomething(){
		j++;		
		eventbus.post(this);
	}

	public int getJ() {
		return j;
	}
}

Hier die Klasse die die Zustandsänderung verarbeiten soll. Das würde deinem Observer entsprechen.

Java:
package eventbusexample;

import com.google.common.eventbus.Subscribe;

public class Receiver {

	@Subscribe
	public void update(ClassA a){
		System.out.println("Klasse A hat sich geändert Wert i ist "+a.getI());
	}

	@Subscribe
	public void update(ClassB b){
		System.out.println("Klasse B hat sich geändert Wert j ist "+b.getJ());
	}
}

Und hier noch ein kleiner Test.

Java:
package eventbusexample;

import com.google.common.eventbus.EventBus;

public class Test {

	public static void main(String[] args) {
		EventBus bus = new EventBus();
		
		ClassA a = new ClassA(bus);
		ClassB b = new ClassB(bus);
		
		Receiver r = new Receiver();
		a.register(r);
		b.register(r);
		
		a.doSomething();
		b.doSomething();
	
	}
}

Die Ausgabe ist:

Code:
Klasse A hat sich geändert Wert i ist 1
Klasse B hat sich geändert Wert j ist 1
 
Na diese Lösung hilft mir nun nicht wirklich weiter. Die Klasse Receiver muss dann ja für jedes Observable-Object, dass sie überwachen kann, eine Methode implementieren. Aber ein observer sollte sich leicht um ein weiteres observable-object erweitern lassen.

in meinem obigen code funktioniert das in etwa so:

Für N observable objekte gibt es auch genau N observer (1:1 beziehung zw observable-observer)

angenommen wir haben observable A, B und C. dann sollte es leicht sein einen observer für A&B, A&C, B&C und A,B&C zu erstellen. Ich möchte eben NICHT für jede mögl. kombination eine neue klasse erstellen.
 
Ich komm nicht ganz mit, gestern wolltest du eine obsever Klasse mit einer Methode pro Observable haben um casts zu vermeiden. Das geht mit meinem Vorschlag. Heute sprichst du von Kombinationen. Mir ist dabei nicht ganz klar was du damit machen willst, aber du kannst auch mehr als eine Receiver-Klasse haben, pro Kombination eine. Neuen code brauchst du doch auch bei deiner Lösung.
 
@I.Haffner Ich habe den letzten Code jetzt mal kurz (!) angeschaut, ggf. muss ich da noch genauer schauen, aber ... das ist ja jetzt vermutlich (!) alles voller Raw-Types und Warnings? Grundsätzlich ist double-dispatch dort wirklich eine Überlegung wert, aber ... ich könnte mir vorstellen, dass man das auch typsicher(er) machen könnte, und... WIE leicht erweiterbar das im Moment ist, da bin ich mir nicht so sicher... ???:L
 

Neue Themen


Zurück
Oben