MVC / Observer

Guten Tag

Als nach der letzten Diskussion meine Anwendung mit Observer schon fast zerfleischt wurde, start ich einen neuen Ablauf.

Dazu ein kleines Beispiel:

    public static void main(String[] args) { 
        new Erzaehler(); 
    } 
} 

class Erzaehler extends Observable { 
     
    public Erzaehler(){ 
        this.addObserver(new Zuhoerer_1()); 
        this.addObserver(new Zuhoerer_2()); 
        tell("hoihoi!"); 
    } 

     
    public void tell(String info){ 
        if(countObservers()>0){ 
            setChanged(); 
            notifyObservers(info); 
        } 
    } 
} 

class Zuhoerer_1 extends JFrame implements Observer{ 
     
    private JTextField field1; 
     
    public Zuhoerer_1(){ 
        field1 = new JTextField("a"); 
        add(field1); 
         
        setTitle("Zuhoerer 1"); 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        setSize(300, 50); 
        setVisible(true); 
    } 
     
    public void update(Observable o, Object arg) { 
        field1.setText((String) arg); 
    } 
} 

class Zuhoerer_2 extends JFrame implements Observer{ 
     
    private JTextField field2; 
     
    public Zuhoerer_2(){ 
        field2 = new JTextField("b"); 
        add(field2); 
         
        setTitle("Zuhoerer 2"); 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        setSize(300, 50); 
        setVisible(true); 
    } 
     
    public void update(Observable o, Object arg) { 
        field2.setText((String) arg); 
    } 
} ```

Was ich jetzt in dem Beispiel nicht verstehe ist, wie sollte ich unterscheiden können wenn ein Observable nur für das eine textfeld ist?

Was meinst Du mit “wenn ein Observable nur für das eine Textfeld ist”? Kannst Du das nochmal genauer erklären?

Ich denke er meint, dass nur ein spezieller Observer der registriert ist benachrichtigt wird. Das geht mMn nicht. Es werden immer alle benachrichtigt.

http://docs.oracle.com/javase/6/docs/api/java/util/Observable.html
„then notify all of its observers“

Ich denke er meint, dass nur ein spezieller Observer der registriert ist benachrichtigt wird. Das geht mMn nicht. Es werden immer alle benachrichtigt.

Ja genau so meinte ich das!

Die Idee ist doch, dass sich ein Observer bei genau dem Observable registriert, für das er sich auch interessiert. Wenn man im Observer wirklich unterscheiden muss, von welchem Observable man jetzt benachrichtigt wurde, dann kann man das natürlich über das erste Argument der update-Methode identifizieren.

Er meint es so, dass ein Observable Objekt z.b. zwei Beobachter hat. Aber er will nur Beobachter1 benachrichtigen. Das geht nicht natürlich könnte man das auch über das Argument lösen, das wäre aber der falsche Ansatz, da dann die Observable Klasse wissen muss, was sie schicken muss und würde somit ihre „Beobachter“ genauer kennen müssen, als das sie nur das Interface Oberserver implementieren.

Wie löst man es dann?

Ich habe ein Model für meine GUI, aus diesem möchte ich die View bei änderungen informieren, mit einem Observer, wie soll ich das dann anstellen?

Die View registriert sich beim Controller, dass sie Aenderungen am Model erhalten moechte. Hier kannst du das ganze noch verfeinern, dass du mehrere Interfaces machst, sodass (nur als Bsp.) die eine View nur Temparaturdaten anzeigen und will daher nur ueber diese informiert werden will, daher gibt es ein Interface TempObserver. Eine andere View moechte nur Farb informationen erhalten, daher gibt es das Interface ColorObserver

Das Model informiert den Controller darueber, dass es Aenderungen (z.B. externe Aenderungen) gegeben hat und welche Daten sich geaendert haben. Da der Controller weisz, dass sich alle Klassen, die sich bei ihm Als TempObserver registriert haben, nur fuer Temparatur Infos interessieren, schickt er diesen auch nur diese Infos. Den ColorObservern nur die Color infos.

Okey das ist sehr schön erklärt danke.

Ich werde dies morgen mal versuchen.

@AmunRa : Gibt es in deiner Sichtweise nur einen einzigen Controller für das gesamte Programm? Oder beschreibst du das Szenario einer “gemischten” GUI, hier bspw. ein JPanel mit mehreren verschiedenen Informationen?

Ich würde mir an der Stelle in Java ist auch eine Insel: Ereignisse über listener anschauen.
http://openbook.galileocomputing.de/javainsel/javainsel_10_002.html#dodtpe24ef2f8-9202-4e69-85d4-0ebdbf70d0ca

Ein wesentlicher Punkt dabei ist, dass eine EventListenerList verwendet wird. Also Composition.
Im Gegensatz zum Erben von Observable, also Inheritance.
Das führt auch dazu, dass mehrere Observables in einer Klasse vereint werden können.

class Erzaehler {
  Observable joke = new Observable();
  Observable aboutWar = new Observable();

  addJokeObserver(Observer o) {
    jokes.addOberver(o);
  }
  addAboutWarObserver(Observer o) {...}

  tellJokes(){...joke...}
  talkAboutWar(){...}
}```

Hier könnte man auch Generics nutzen und die Observables in einer Map vorhalten.

So wie ich das sehe ist es auch semantisch sinnvoller das ganze Event und EventListener zu nennen, da ein Zähler ja einen Event sendet. Es sendet die Daten mit. Ein Observable ändert hingegen seinen Zustand. Eine Tankanzeige wäre z.B. eher etwas dass ich Observable nennen würde. Hier wird mitgeteilt, dass sich der Zustand geändert hat und dann nachgesehen werden muss wie nun der aktuelle Stand ist. 

Zudem gibt es mittlerweile noch mit javafx, JavaFx-Beans und diverse PropertyListener, die auch in diese Richtung gehen ein Model und eine View miteinander zu synchronisieren.

Ganz klar nein.

Ich wollte eher beschreiben, wie es für headnut möglich ist, dass nur ein spezieller Observer informiert wird. bzw. eben, dass es darum geht, dass der(die) Controller die Möglichkeit hat(haben), durch verschiedebe Interfaces verschiedene Informationen preis zu geben.
@headnut
Hab vl hier noch ein besseres Bsp.
Ein JList bietet nicht nur die möglichkeit sich mit einem Listener für alle Ereignisse zu registrieren, sondern für die verschiedenen Arten (wie der z.B der User damit interagieren kann) einen eigenen. Für Maus-Events z.B. MouseListener, für Tastatur-Events KeyListener - für Auswahl der Einträge den SelectionListener. Hier sieht man z.B. wieder, dass man mehrere Controller haben kann, da die ersten beiden Bsps. von JList selbst (bzw. Ihrern Vaterklassen) verwaltet werden, und der SelectionListener über das SelectionModel informiert wird.

Vielleicht erkläre ich, wie ich es bis jetzt gehändelt habe.

Eure Ansätze sind sensationell danke schonmal, nur finde ich es für meine einfachen GUI’s schon fast zuviel…

Ich habe bei meinen Projekten meistens eine Datenbank mit verschiedenen Tabellen, und ein bis zwei Teilnehmer per TCP. Die Tabellen aktualisieren sich über ihr Model und die Datenbank selbst. Höchstens wenn ein neuer Artikel angelegt wird, bekommen sie per Observer die Info dies zu tun, in ihren Datenbanken.

Die Artikelinfos die die TCP Teilnehmer benötigen, lesen sie direkt aus den DB’s.

Die Infos die Per TCP kommen werden der GUI über das Model zur verfügung gestellt.

Mein Model schickte alle 200ms eine Info an die GUI dass sie neue Informationen holen soll:

	 * Aktualisierungs invtervall des Panels
	 */
	private void aktualPanel() {
		TimerTask action = new TimerTask() {
			public void run() {
				setChanged();
				notifyObservers(TexteObserver.GUIRELOAD);
			}
		};
		Timer caretaker = new Timer();
		caretaker.schedule(action, 3000L, 250);
	}```

Über diverse Getter holt sich die GUI die Informationen:

```	/**
	 * Gibt die aktuelle Artikelnummer zurück
	 * 
	 * @return String
	 */
	public String getArtikelnummer() {
		Object o = modelParameter.getDBEintrag(1);

		if (o == null) {
			return "";
		}
		return (String) o;

	}```

In der VIEW dann, wird das Objekt dass vom Observer geschickt wird zu einem String gecastet und verglichen:

```	public void update(Observable o, Object arg) {
		if (model == o) {

			// Alle Parameter aktualisieren
			if (arg.equals(TexteObserver.GUIRELOAD)) {
				txtArtikelNummer.setText(model.getArtikelnummer());
				txtProduktName.setText(model.getProduktName());
				txtPosHolenRob1.setText(model.getPosHolenRob1());
				txtPosHolenRob2.setText(model.getPosHolenRob2());

				jcbLegeBild.setSelectedIndex(model.getBildBox());

				txtGewichtBox.setText(model.getSollGewBox());
				txtGewichtStk.setText(model.getSollGewStk());
				txtTolBoxMax.setText(model.getToleranzBoxMax());
				txtTolBoxMin.setText(model.getToleranzBoxMin());
				txtTolStkMax.setText(model.getToleranzKäseMax());
				txtTolStkMin.setText(model.getToleranzKäseMin());
				txtJobNrCam.setText(model.getJobNrCam());
				txtCamPos.setText(model.getCamPos());
				txtBreiteStk.setText(model.getBreiteStk());
				txtGreifferName.setText(model.getGreifferName());
				txtGreifferWinkel.setText(model.getGreifferWinkel());
				txtBeschl.setText(model.getBeschleunigung());
				txtVerz.setText(model.getVerzögerung());
				txtAnzKaese.setText(model.getAnzKaese());
				txtProdHelp.setText(model.getProdHelp());

				// CheckBoxen
				chkBCDoben.setSelected(model.getIoDatenbank(33));
				chkBCDunten.setSelected(model.getIoDatenbank(34));
				chkLigne6.setSelected(model.getIoDatenbank(35));
				chkLigne7.setSelected(model.getIoDatenbank(36));

				// Eingaben sperren
				if (model.getFixGewichtAngew()) {

					chkFixGew.setSelected(true);

					lblGewichtBox.setVisible(false);
					txtGewichtBox.setVisible(false);

					lblTolBoxMax.setVisible(false);
					txtTolBoxMax.setVisible(false);

					lblTolBoxMin.setVisible(false);
					txtTolBoxMin.setVisible(false);

					lblTolStkMax.setVisible(true);
					txtTolStkMax.setVisible(true);

					lblTolStkMin.setVisible(true);
					txtTolStkMin.setVisible(true);

					lblAnzKaese.setVisible(true);
					txtAnzKaese.setVisible(true);

				} else {

					chkFixGew.setSelected(false);

					lblGewichtBox.setVisible(true);
					txtGewichtBox.setVisible(true);

					lblTolBoxMax.setVisible(true);
					txtTolBoxMax.setVisible(true);

					lblTolBoxMin.setVisible(true);
					txtTolBoxMin.setVisible(true);

					lblAnzKaese.setVisible(false);
					txtAnzKaese.setVisible(false);

					lblTolStkMax.setVisible(false);
					txtTolStkMax.setVisible(false);

					lblTolStkMin.setVisible(false);
					txtTolStkMin.setVisible(false);
				}

			}
		}

	}```

Ich hoffe man versteht dies ein wenig, mein Problem ist eben dass die relativ aufwändig ist... Was habt ihr für Ansätze bei solchen Anwendungen? Vielleicht ist ja Observer das falsche? Auf jedenfall schaue ich mir die genannten Ansätze nun genauer an...

Auch, wenn es eigentlich keine Änderung im Model gab? In meinen Augen ein Design-Fehler. :reflect:

Japp immer :smiley:

Ist das einfachste fand ich, muss nicht überall sagen dass jetzt eine Änderung kam…

Idealerweise erfolgt der DB Zugriff auf dem Applikationsserver durch die Serversoftware und nicht direkt durch den TCP Client. Evlt. hierfür interessant Web Services.

Würde jetzt nicht sagen, das Observer allgemein und die Java Implementierung im Speziellen hier falsch ist. Ich mag die Implementierung allerdings nicht und bevorzuge ein Listener Interface ggf. mehreren verschiedenen Methoden - das erspart auf Observer/Listener Seite eine Fallunterscheidung, abhänging vom übergebenen Objekt.

weil sich in der GUI angezeigte Daten in der Datenbank regelmässig ändern (können)? (Wie werden dann überhaupt die Daten im Model geändert?)
Das wäre eher eine Anforderung der View und diese sollte einfache alle x ms die Daten des Models holen/vergleichen. Alternativ überprüft das Model alle x ms gegenüber der DB die Aktualität seiner Daten und benachrichtigt nur im Bedarfsfall seine Observer.

[QUOTE=_Michael;65518]weil sich in der GUI angezeigte Daten in der Datenbank regelmässig ändern (können)? (Wie werden dann überhaupt die Daten im Model geändert?)
Das wäre eher eine Anforderung der View und diese sollte einfache alle x ms die Daten des Models holen/vergleichen. Alternativ überprüft das Model alle x ms gegenüber der DB die Aktualität seiner Daten und benachrichtigt nur im Bedarfsfall seine Observer.[/QUOTE]

Ja die ändern sich regelmäßig. Sei dies aktuelle Temperaturen, Längenangaben, oder oder oder…

Die View’s holen sich eben genau diese Daten aus dem Model, über den Observer kommt nur der Befehl dass sie dies müssen.

Idealerweise erfolgt der DB Zugriff auf dem Applikationsserver durch die Serversoftware und nicht direkt durch den TCP Client. Evlt. hierfür interessant Web Services.

Naja es ist im gleichen Netzwerk, der Applikationsserver in dem Sinne gibt es schon, Er „übersetzt“ die Daten in ein Protokoll das gesendet werden kann.

Wann und wie gelangen diese Änderungen ins Model würde es dann nicht ausreichen einfach zu diesen Zeitpunkten den Observer zu informieren?

Das Model hat Zugriff auf getter in der TCP Applikation. Sobald die view Informationen anfordert, holt sich das Model diese an der entsprechenden stelle.

es dann nicht ausreichen einfach zu diesen Zeitpunkten den Observer zu informieren?

Die GUI würde sich vielmehr aktualisieren, da die Daten eigentlich nach jedem Empfangen eines TCP Protokolls anders sind. Deshalb hab ich das so ein wenig geglättet.

Naja mach eine Mischung.

A)Das Model hat Zugriff auf getter in der TCP Applikation und kann daher informationen bereitstellen.
B)Der Controller wird informiert, wenn sich Daten aendern und auch welcher Typ von Daten geaendert wurde. Eleganterweise könntest du z.B. für jeden Typ von Daten einen eigenen Controller definieren (vl auch für jeden Typ von Daten ein eigenes Modelobject).
C)Die Views zeigen nur Daten eines Typs an und implementieren daher für jeden Typ ein eigenes Interface.

1 der Controller weis nun , wann sich welche Daten aendern und speichert dies ab z.B. boolean hasTemperatureChanged, hasLengthChanged
2 Die View registriert sich beim Controller ueber welche aenderungen sie informiert werden moechte. z.B. Temparatur
3 Der Controller informiert nicht sofort, wenn sich die Daten aendern, die Views, sondern wartet ein gewisses Zeitfenster
4 Wenn das Zeitfenster verstrichen ist, informiert es alle Views, die sich für Temparaturangaben interressieren, nur wenn “hasTemperatureChanged” true ist, alle Views die Längeninfos haben möchten, nur wenn “hasLengthChanged” true ist.
5) Die Views hohlen nun beim Model die neuen Daten ab.

Dadurch müssen sich nicht immer alle Views aktualisieren.