Best Practice AllroundListener - zu gebrauchen?

goodcat

Aktives Mitglied
Moin,

ich wollte mal meine so gut wie nicht existierende "Code-Sammlung" erweitern und habe mal einen Listener geschrieben den ich in einem Programm mit GUI benutzt habe. Ich hatte in dem Programm das Problem das ich teilweise die gleichen Daten in verschiedenen JInternalFrames hatte, sich aber nach dem verändern nicht alle InternalFrames aktualisiert haben (die Daten kamen aus einer MySQL DB)... Deshalb ein (wenn ich das so nennen darf?) ein Klassen übergreifender EventListener.

Hier meine Klassen... (bin noch nicht so lange am programmieren)

Listener.java
Java:
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

public class Listener {

    private Set<ListenerInterface> listeners = null;

    public Listener() {
    	this.listeners = new HashSet<ListenerInterface>();
    }
    
    
    /**
     * @param listener - an object which implements the ListenerInterface 
     * @return true if this EventListener did not already contain the specified element
     * @throws IllegalArgumentException if the specified element is null 
     */
    public boolean add(ListenerInterface listener) {
    	if (listener == null) {
    		throw new IllegalArgumentException("cannot add null to a nonNullSet!");	
    	}
    	else {
        	if (!listeners.contains(listener)) { // <- die if Abfrage kann ich mir wahrscheinlich sparen da ein Hashset benutzt wird... aber aus unerfindlichen gründen hatte ich in dem ursprünglichen Programm ohne die Abfrage Probleme...
        		return this.listeners.add(listener);
        	}    		
        }
    	
    	return false;    	
    }
    
    /**
     * @param listener - an object which implements the ListenerInterface
     * @return true if this EventListener contained the specified element
     * @throws IllegalArgumentException if the specified element is null
     */
    public boolean remove(ListenerInterface listener) {
    	if (listener == null) {
    		throw new IllegalArgumentException("cannot remove null from a nonNullSet!");	
    	}
    	else {
    		return this.listeners.remove(listener);  		
        }
    }
    
    /**
     * Iterates through the set with Listeners and runs the refresh method.
     * @throws NoSuchElementException if the Listener set is empty
     */
    public void start() {
    	try {
        	Iterator<ListenerInterface> it = this.listeners.iterator();
        	
        	if (it.hasNext()) {
        		while (it.hasNext()) {
                	it.next().refresh();
                }
        	}
        	else {
        		throw new NoSuchElementException("no elements in Listener Set");
        	}
        }
    	catch(Exception e) {
        	e.printStackTrace();
        }
    }
}

ListenerInterface.java
Java:
public interface ListenerInterface {

	public boolean add(Listener listener);
	public boolean remove(Listener listener);
	public void refresh();
	
}

TestCase1.java
Java:
import EventListener.Listener;
import EventListener.ListenerInterface;

public class TestCase1 implements ListenerInterface {

	public TestCase1() {
	}
	
	public TestCase1(Listener listener) {
		add(listener);
	}
	
	@Override
	public void refresh() {
		System.out.println("Run TestCase1 Class");
	}

	@Override
	public boolean add(Listener listener) {
		return listener.add(this);
	}

	@Override
	public boolean remove(Listener listener) {
		return listener.remove(this);
	}

}

TestCase2.java
Java:
import EventListener.Listener;
import EventListener.ListenerInterface;

public class TestCase2 implements ListenerInterface {

	public TestCase2() {
	}
	
	public TestCase2(Listener listener) {
		add(listener);
	}
	
	@Override
	public void refresh() {
		System.out.println("Run TestCase2 Class");
	}

	@Override
	public boolean add(Listener listener) {
		return listener.add(this);
	}

	@Override
	public boolean remove(Listener listener) {
		return listener.remove(this);
	}
}

Main.java
Java:
import EventListener.Listener;

public class Main {

	public static void main(String[] args) {
		Listener myEventListener = new Listener();
		
//		TestCase1 testCase1 = new TestCase1(myEventListener);
//		TestCase2 testCase2 = new TestCase2(myEventListener);
		
		TestCase1 testCase1 = new TestCase1();
		testCase1.add(myEventListener);
		TestCase2 testCase2 = new TestCase2();
		testCase2.add(myEventListener);
		
		myEventListener.start();
	}

}

Ich habe in dem Interface zusätzlich eine add() und remove() Methode um die Klasse von extern oder direkt aus der Klasse heraus aus dem Listener Set zu entfernen. Macht das sinn? Eigentlich überflüssig da ich auch ohne die beiden Methoden zu implementieren die Klasse aus dem Listener entfernen kann (von extern sowie intern). Oder ist die Implementierung mit den Methoden ein besserer "Stil"?

Ich wollte mal ganz allgemein eure Meinungen zu dem Code einholen, ob der Listener zu gebrauchen oder noch zu verbessern etc. ist. Und vielleicht kann ja der eine oder andere den Code gebrauchen. Ich würde mich über euer Feedback sehr freuen.

Gruß
goodcat
 
S

Spacerat

Gast
Das sieht mir mehr nach dem Observer Pattern aus. Sinn machts deswegen nicht, weil in Java bereits vorhanden. Dieses unterscheidet sich von Event Pattern in einem zusätzlichen Thread. Objekte die Events werfen, übergeben diese an eine Event-Queue bei welcher sich letztendlich auch die Listener registrieren. Die Queue arbeitet in einem anderen Thread die Liste der geworfenen Events ab und verteilt sie an die Listener. Beim Observer Pattern verteilt jedes Observable Zustandsänderungen selbständig im gleichen Thread an registrierte Observer (sihe deine "start()"-Methode).
 

goodcat

Aktives Mitglied
Ok. Da habe ich eigentlich ein eigenes Observer/Oberservable Pattern gebaut...

Noch ne Frage dazu.
Was macht man denn wenn der Observer auch Oberservable ist. Ist das nicht ein klassisches Problem bei GUIs, wenn die eine GUI Daten aus der anderen GUI beinhaltet? Ich habe das mal ausprobiert und es funktioniert auch, aber ist das ein praktikable Lösung? Allerdings finde ich das sehr unübersichtlich...
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Java:
class MyObservable {
  private final PrivateObservable dispatcher = new PrivateObservable();

  MyObservable() {
  }

  public void addObserver(Observer obs) {
    dispatcher.addObserver(obs);
  }

  public void removeObserver(Observer obs) {
    dispatcher.deleteObserver(obs);
  }

  public synchronized void start() {
    dispatcher.setChanged();
    dispatcher.notifyObservers();
  }

  private static final class PrivateObservable() extends Observable {
    @Override
    public void setChanged() {
      super.setChanged();
    }
  }
}
So implementierst du das Vorhandene... Der private Dispatcher ist notwendig, wenn die Klasse eine weitere Klasse erweitern soll. Dies muss eine eigene Klasse sein, in welcher "setChanged()" öffentlich gemacht wurde.
Ein EventSystem ist da aber schon um einiges anspruchsvoller von wegen des Threads und der Quesynchronisierung.
 
Zuletzt bearbeitet von einem Moderator:
S

Spacerat

Gast
Oh? Tatsächlich? Mag sein. Ich hatte mein Eventsystem lange vor 1.5 fertig, aber hab' anschliessend trotzdem mal nach gesucht. Konnte aber nur 'ne konkrete Klasse ArrayDequeue finden. Die einzige Collection, die nach wie vor von vorne herein threadsicher ist, war der Vector. Naja, weil meine EventQueue ohnehin schon threadsicher war, habe ich die Suche recht rasch eingestellt, es gelassen, wie es war und mir obendrein die Lektüre über ebenfalls neue Concurrency-API erspart.
 

Bernd Hohmann

Top Contributor
Oh? Tatsächlich? Mag sein. Ich hatte mein Eventsystem lange vor 1.5 fertig, aber hab' anschliessend trotzdem mal nach gesucht.

Ich ja auch, aber weil ich immer schief angeguckt werde wenn ich die Klasse aus der Schublade ziehe will ich für neuen Kram was aus der RT nehmen.

Die einzige Collection, die nach wie vor von vorne herein threadsicher ist, war der Vector.

Und das nicht mal so richtig: man kann ein add(...) gleichzeitig zum clear() durchführen und sich freuen :)

LG,
Bernd
 

Ullenboom

Bekanntes Mitglied
Und das nicht mal so richtig: man kann ein add(...) gleichzeitig zum clear() durchführen und sich freuen
clear() ist zwar nicht synchronized, ruft aber direkt im Rumpf removeAllElements() auf, was als alte Java 1.0 Vector-Methode synrchronized ist. Das kommt also als gleiche raus und man hat mit add() und clear() zwei synchr. Methoden und nichts kann schiefgehen.
 

goodcat

Aktives Mitglied
Ok also das mit dem Private Observable leuchtet mir ein in meinen Test hatte ich nämlich auch festgestellt das setChanged() Protected ist und auf diese Methode von außen nicht zugegriffen werden kann.

Allerdings funktioniert dein Code bei mir nicht ich habe einige Fehler und konnte durch kurzes testen nicht rauskriegen wo genau der Fehler zu finden ist (Muss ich mir in Ruhe anschauen). Ich habe das bevor Du den Code gepostest hast so gelöst:

Java:
package Observer;

import java.util.Observable;

public class MyObserver extends Observable {

	@Override
	public void setChanged() {
		setChanged();
		notifyObservers();
	}

}

Sollte doch im Endeffekt das selbe sein, oder?
 
S

Spacerat

Gast
Ja, aber erstens ist "setChanged()" erreichbar, wenn du direkt Observable erweiterst und zweitens sollte diese Methode keinesfall öffentlich gemacht werden, weil sonst jeder von ausserhalb bestimmen kann, wann das Observable geändert wurde und annschliessend "notifyObservers()" penetrieren (und zwar ohne, dass das Objekt wirklich geändert wurde).
Der private Dispatcher wird nur benötigt, wenn du 'ne andere Klasse erweitern möchtest.
Java:
MyObservable extends BufferedImage {
  private final PrivateObservable dispatcher = ... usw.
}
Naja... BufferedImage ist zwar ein schlechtes Beispiel, aber das 'ne andere Geschichte.
 
Zuletzt bearbeitet von einem Moderator:

goodcat

Aktives Mitglied
Ja das stimmt wohl das notifyObservers(); sollte ich da definitiv rausnehmen!!
Das mit dem private Dispatcher habe ich noch nicht so ganz begriffen... Muss ich mir heute nochmal in Ruhe anschauen. Ich werde mal eine neue Klasse auf der Grundlage deines Codes erstellen und dann posten.

Danke!
 
S

Spacerat

Gast
NEIIIIIN... Es genügt nicht, nur "notifyObservers()" zu entfernen, sondern du musst "setChanged()" wieder protected machen und deswegen musst du diese Methode nicht mal mehr überschreiben.
Es geht ganz einfach darum, dass nur dass Observable "setChanged()" aufrufen können darf. Der private Dispatcher muss diese Methode public machen, weil sie sonst nicht mal von der delegierenden Klasse aufgerufen werden kann (obwohl... ich hab's inzwischen ausprobiert... protected funktioniert ja auch... ???:L).
 

goodcat

Aktives Mitglied
Ich habe deine Klasse genommen und eine Kleinigkeit verändert:
Java:
private static final PrivateObservable dispatcher = new PrivateObservable();

Und zwar habe ich den dispatcher static gemacht und nur eine Instanz vom PrivateObservable zu haben damit ich wenn ich eine Klasse mit MyObservable erweitere immer eine Instanz habe. Ist das so in Ordnung?

setChanged() bzw. start() können nur Klassen aufrufen die um MyObservable erweitert werden.
Um mein Problem zu lösen das der Observer auch Obersevable sein kann erweitere ich einfach die Klasse mit MyObservable. Diese Klasse kann dann auch start() aufrufen.

Kann ich das so benutzen bzw. wäre das so in Ordnung?
 
S

Spacerat

Gast
Keine Chance! "static" is nix gut. Ganz einfach weil dieses Observable nun auch all jene Observer informiert, die nicht in der spezifizierten Klasse registriert wurden. Observer, welche sich bei einem anderen Objekt registriert haben, würden nun auch dann informiert werden, wenn sich ein anderes Observable verändert hat.
Ich habe für meine Zwecke ein eigenes ObserverPattern gebastelt, welches im Gegensatz zu dem Original nicht mit Observables und Objecten, sondern mit ObserverActions um sich wirft.
Das Interface Observable:
Java:
public interface Observable {
	void addObserver(Observer obs);
	void removeObserver(Observer obs);
}
Das Interface Observer:
Java:
public interface Observer {
	void update(ObserverAction action);
}
Die Klasse ObservableDispatcher (nicht ohne Grund final ;)):
Java:
import java.util.Iterator;
import java.util.List;

import java.utils.ArrayList;

public final class ObservableDispatcher {
	private final List<Observer> listeners = new ArrayList<Observer>();

	public final void notifyObservers(final ObserverAction action) {
		synchronized (listeners) {
			Iterator<Observer> it = listeners.iterator();
			while(it.hasNext()) {
				Observer obs = it.next();
				obs.update(action);
				if(action != null && action.removeNotifier) {
					it.remove();
					action.removeNotifier = false;
				}
			}
		}
	}

	public final void addObserver(Observer obs) {
		synchronized (listeners) {
			listeners.add(obs);
		}
	}

	public final void removeObserver(Observer obs) {
		synchronized (listeners) {
			listeners.remove(obs);
		}
	}
}
Und zuletzt die Klasse ObserverAction (kann mit neuen und alten Werten instanziert werden, muss aber nicht):
Java:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public final class ObserverAction {
	private final List<Object> newValues, oldValues;
	private final Observable source;
	boolean removeNotifier;

	public ObserverAction(Observable source) {
		this(source, source, null);
	}

	public ObserverAction(Observable source, Object newValue, Object oldValue) {
		if(source == null) {
			throw new NullPointerException();
		}
		List<Object> newValues, oldValues;
		if(newValue == null || ((newValue instanceof Collection) && ((Collection<?>) newValue).isEmpty())) {
			newValues = Collections.emptyList();
		} else {
			newValues = new ArrayList<Object>();
			if(newValue instanceof Collection) {
				newValues.addAll((Collection<?>) newValue);
			} else {
				newValues.add(newValue);
			}
			newValues = Collections.unmodifiableList(newValues);
		}
		if(oldValue == null || ((oldValue instanceof Collection) && ((Collection<?>) oldValue).isEmpty())) {
			oldValues = Collections.emptyList();
		} else {
			oldValues = new ArrayList<Object>();
			if(oldValue instanceof Collection) {
				oldValues.addAll((Collection<?>) oldValue);
			} else {
				oldValues.add(oldValue);
			}
			oldValues = Collections.unmodifiableList(oldValues);
		}
		this.newValues = newValues;
		this.oldValues = oldValues;
		this.source = source;
	}

	public final List<Object> getNewValues() {
		return newValues;
	}

	public final List<Object> getOldValues() {
		return oldValues;
	}

	public final Object firstNew() {
		for(Object o : newValues) {
			return o;
		}
		return null;
	}

	public final Object firstOld() {
		for(Object o : oldValues) {
			return o;
		}
		return null;
	}

	public final Observable getSource() {
		return source;
	}

	public void notifyRemove() {
		removeNotifier = true;
	}
}
Deine Observables müssen nun nur noch Observable implementieren und einen privaten ObservableDispatcher instanzieren.
Das "addObserver()" ist hier allerdings ein wenig "verunglückt", weil ich statt der normalen ArrayList ein IdentityHashSet, bzw. eine IdentityArrayList verwende.
 
Zuletzt bearbeitet von einem Moderator:

goodcat

Aktives Mitglied
Erstmal Vielen Dank! Aber ich verstehe nicht ganz wo zum Original der Unterschied besteht. Du hast geschrieben das das Original mit Observables und Objecten um sich wirft und dein Code mit ObserverActions, ok. Ich hab nicht ganz verstanden wo der Vorteil bei dem Code liegt, was bringen die Actions? Kannst Du eventuell noch einen kleinen Beispielcode dazupacken wie das zu verwenden ist? Vielleicht wird es für mich dann etwas verständlicher.
 
S

Spacerat

Gast
Natürlich gibt's da nur wenig Unterschiede, dafür ist Observable ja auch ein Designpattern. Würde es grosse Unterschiede geben, wärs am Ende unter Umständen nicht mehr das Observable Pattern.
Der gravierendste Unterschied ist, das meine Observer evtl. Zugriff auf die alten Daten bekommen, das ist hilfreich, wenn ein Observer z.B. mit Differenzdaten arbeitet.
Als Anwendungsbeispiel hab' ich mich für zwei Klassen meiner Control-API entschieden:

1. Die abstrakte Klasse Control:
Java:
public abstract class Control implements Observable {
	public static abstract class Type {
		private final String name;
		private final Class<?> typeClass;

		protected Type(String name, Class<?> typeClass) {
			if(name.trim().length() == 0) {
				throw new IllegalArgumentException("invalid name");
			}
			if(typeClass == null || typeClass == void.class || typeClass == Void.class) {
				throw new IllegalArgumentException("invalid type class");
			}
			this.name = name;
			this.typeClass = typeClass;
		}

		public final String getName() {
			return name;
		}

		@Override
		public boolean equals(Object obj) {
			if(this == obj) {
				return true;
			}
			if(obj instanceof Type) {
				return name.equals(name) && typeClass.equals(typeClass);
			}
			return false;
		}

		@Override
		public int hashCode() {
			return name.hashCode() * 31 + typeClass.hashCode();
		}

		@Override
		public final String toString() {
			return name;
		}

		public final Class<?> getTypeClass() {
			return typeClass;
		}
	}

	private final Type type;
	protected final ObservableDispatcher dispatcher = new ObservableDispatcher();

	Control(Type type) {
		if(type == null) {
			throw new NullPointerException();
		}
		this.type = type;
	}

	public final Type getType() {
		return type;
	}

	@Override
	public final void addObserver(Observer obs) {
		dispatcher.addObserver(obs);
	}

	@Override
	public final void removeObserver(Observer obs) {
		dispatcher.removeObserver(obs);
	}

	@Override
	public String toString() {
		return type.name;
	}
}
2. Eine konkrete Control-Implementation (z.B. DoubleControl):
Java:
public abstract class DoubleControl extends Control {
	protected static class Type extends Control.Type {
		public Type(String name) {
			super(name, double.class);
		}
	}

	private double newValue, oldValue, min, max;

	public DoubleControl(String name) {
		this(new Type(name));
	}

	protected DoubleControl(DoubleControl.Type type) {
		super(type);
		min = Double.NEGATIVE_INFINITY;
		max = Double.POSITIVE_INFINITY;
	}

	public final double getValue() {
		return newValue;
	}

	public final double getMin() {
		return min;
	}

	public final double getMax() {
		return max;
	}

	protected void setMin(double min) {
		setBorder(min, max);
	}

	protected void setMax(double max) {
		setBorder(min, max);
	}

	protected void setBorder(double a, double b) {
		double min = Math.min(a, b);
		double max = Math.max(a, b);
		if(this.min == min && this.max == max) {
			return;
		}
		this.min = min;
		this.max = max;
		setValue(newValue);
	}

	public final void setValue(double value) {
		if(value < min) {
			value = min;
		}
		if(value > max) {
			value = max;
		}
		if(this.newValue == value) {
			return;
		}
		oldValue = newValue;
		newValue = value;
		dispatcher.notifyObservers(new ObserverAction(this, newValue, oldValue));
	}
}
Implementationen für Short-, Char-, Integer-, Long- und FloatControl funktionieren genauso, Boolean- und ObjectControls ein wenig anders. Fakt ist, dass die Parameter newValue und oldValue auch null sein bzw. ganz weggelassen werden dürfen. Bei der JVM Implementation hat man nur die Möglichkeit ein Objekt zu übergeben dass alles beinhalten kann aber nichts konkretes darstellt.
Ich spiele im übrigen auch mit dem Gedanken, aus new- und oldValue VarArgs zu machen, dann spar' ich mir (hoffentlich) dieses Listengezuppel in der ObserverAction.
[EDIT]Als nächstes kommt dann wohl die Frage, was es mit solchen Controls auf sich haben kann... Damit kann man z.B. bei mehreren Audiokanälen gleichzeitig die Lautstärke ändern, irgendwelche abstrakten RenderEngines, AudioServices oder GraphicsContexts konfigurieren usw.[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:

goodcat

Aktives Mitglied
Krass sehr interessant!! ja bin da nicht komplett durchgestiegen. :rtfm:
Nochmal zu meiner static Geschichte. Ich habe das static gemacht gerade weil ich immer alle Observer benachrichten wollte. Klar die Klasse kann nur für einen Zweck benutzt werden. Ich habe halt immer das Problem gehabt das ich verschiedene Oberflächen benachrichtigen wollte und keine Lust hatte immer das Observable Object im Konstruktor mitzuschleifen (deshalb static) aber bei genauerer Überlegung hätte man das Wahrscheinlich mit Vererbung umgehen können. Wie heisst es so schön wer es sich einfach machen will benutzt static :D... wobei man das nicht immer so sagen kann. Vielen Dank für deine Hilfe. Ich werde mir meine GUI Klassen nochmal genauer anschauen und gucken was ich da mit Vererbung noch machen kann. Aber was das angeht bin ich noch nicht so richtig fit :(
 

goodcat

Aktives Mitglied
Mh also ich habe mir das ganze nochmal durch den Kopf gehen lassen aber komme nicht weiter.

Wenn ich mehrere JInternalFrames habe und aus den GUIs neue JInternalFrames erstellt werden und ich alle JInternalFrames aktualisieren möchte dann bleiben mir doch nur 2 Möglichkeiten.
1. Eine JInternalFrame Masterklasse erstellen (z.B. abstract) und dort ein static Observable Object erstellen. Damit ich von allen Klassen die von diesem Object erweitert wurden alle Oberflächen aktualisieren kann.
2. In den Klassen die dieses Observer Pattern nutzen sollen im Konstruktor immer ein Observer Object übergeben.

Meiner Meinung nach sind beide Varianten mist! Die erste fände ich ganz gut aber halt static und damit .. Naja die Meinungen gehen auseinander :D
Und die zweite kompliziert nur den Code und die ganze Zeit in weis der Geier wie viele Ebenen ein Observer Object durchzuschleifen ist genau so bescheuert.

Was kann ich tun ob dieses Problem zu lösen? Hast Du da eventuell ein paar Links oder Quellen wo ich mehr darüber erfahren kann. Oder kann mir jemand kurz erklären wie man dieses Problem angehen kann?

Danke,
goodcat
 
S

Spacerat

Gast
Das Geheimnis liegt im Observer selber würd' ich sagen...
Java:
class MyObservableObserver implements Observable, Observer {
  private final ObservableDispatcher dispatcher = new ObservableDispatcher();

  public void addObserver(Observer obs) {
    dispatcher.addObserver(obs);
  }

  public void removeObserver(Observer obs) {
    dispatcher.removeObserver(obs);
  }

  public void update(ObserverAction action) {
    // do updates
    dispatcher.notifyObservers(action); // und weiter reichen.
  }
}
... aber schon heisst es Vorsicht Kreisbezug! Achte peinlich darauf, was du wohin addest, nicht dass ein Observer in zwei Observables auftaucht, die sich gegenseitig registriert haben. Schöner geht's, wenn die geaddeten Kind-Objekte allesamt einen privaten Observer geaddet bekommen. Wie beim Dispatcher, blos in die andere Richtung.
 

goodcat

Aktives Mitglied
Irgendein Controller o.ä., der die Frames erstellt registriert sich bei diesen und gibt die Events weiter.

Problem ist nur das teilweise aus einem JInternalFrame heraus ein weiteres JInternalFrame erstellt wird also teilweise verschachtelt. Und dann müsste ich diesen einen Controller ja im Konstruktor übergeben. Oder habe ich da was falsch verstanden?! Die GUIs verwenden alle das MVC Prinzip bzw. benutzt jede Gui Ihr eigenes MVC.

Ach das nervt mich alles :( ich komme einfach was die Struktur angeht also Vererbung, Erweiterung nicht weiter. Mir fehlt da einfach n Stück weit die Erfahrung was OOP angeht und die bekannten Java Bücher (Java ist auch eine Insel etc. <- Super Buch!) helfen da auch nicht weiter. Kennt sonst einer noch ein gutes Buch für "Fortgeschrittene Anfänger" was sich vielleicht auf die Pattern in GUIs bezieht?
 

Neue Themen


Oben