Einführung Inhaltsverzeichnis 2. Darstellung von Variablen und Werten

8. MVC mit Observer

Das Observer-Muster ist geeignet Views zu implementieren. Observable entspricht dann Model und Control zusammengelegt. Wenn man sie auseinanderdividiert, ist es zweckmäßig, Control als Observable zu programmieren und Model ist dann auch ein Observer, der auf die Benachrichtigungen von Control lauscht. Model und View müssen sich so bei Control für die Benachrichtigung registrieren:

Control erweitert also Observable:

public class Control extends Observable { … } // Control.java

Control erhält View nun nicht über Konstruktorparameter, sondern über addObserver() in main:

public static void main(String[] args) { // Main.java

   IModel model = new Model();

   Control control = new Control();

   control.addObserver(model);

   control.addObserver(new KreisView(model));

   control.addObserver(new DigitalView(model));

   control.start(); }

Die Methode Control.start() erledigt Initialisierungsarbeiten, die im Konstruktor noch zu früh wären, da zu diesem Zeitpunkt die Observer noch nicht hinzugefügt worden sind: das Erzeugen und Übergabe der Lauscher. Dies geschieht auch über notifyObservers():

public void start() { // Control.java

   setChanged(); // erforderlich für Observable

   notifyObservers(new ActionListener() { // Ereignisbehandlungsobjekt übergeben

     @Override

     public void actionPerformed(ActionEvent e) {

        setChanged(); // erforderlich für Observable

        notifyObservers(e.getActionCommand()); } } ); }

        // Parameter arg = Beschriftung des Knopfes "Ein", "Aus" oder "Um"     

KreisView reagiert auf jede Benachrichtigung gleich, unabhängig vom Ereignis:

@Override // Observer // KreisView.java

public void update(Observable o, Object arg) {

   repaint(); }

Die andren Observers (Model und DigitalView) müssen aufgrund des erhaltenen Parameters arg von update() unterscheiden, was für ein Ereignis sie aufgerufen hat. Dieser ist derselbe, den Control an notifyObservers() übergeben hat (oben: Beschriftung der Knöpfe bzw. ActionListener). Beispielsweise implementiert DigitalView die Schnittstelle Observer, daher enthält sie update():

@Override // Observer // DigitalView.java

public void update(Observable o, Object arg) {

   if (arg.getClass().equals(String.class)) { // Oberflächenereignis?

     String signal = (String)arg; // hässlich

     switch (signal) {

        case "Ein": knopfEin.setEnabled(false);

           knopfAus.setEnabled(true); break;

        case "Aus": knopfEin.setEnabled(true);

           knopfAus.setEnabled(false); break;

        case "Tick": Uhrzeit uhrzeit = model.getUhrzeit();

           anzeige.setText(String.format("%02d:%02d:%02d", uhrzeit.getStunde(), uhrzeit.getMinute(), uhrzeit.getSekunde())); } }

        // keine Oberflächenreaktion auf "Um"

   else { // Initialisierungsereignis von Control

     ActionListener lauscher = (ActionListener)arg; // gefährlich 

     knopfEin.addActionListener(lauscher);

     knopfAus.addActionListener(lauscher);

     knopfUm.addActionListener(lauscher); } }

Wie die Kommentare es andeuten, ist die Konvertierung (casting) des erhaltenen Objects hässlich, gar gefährlich (wenn vorher sein Typ nicht überprüft wird: Sie kann ClassCastException auswerfen). Dies liegt daran, dass java.util.Observer und Observable zu den ältesten Java-Bibliothekelementen gehören: Sie wurden noch vor der Einführung von generischen Einheiten entworfen.

In IView und IModel werden jetzt die Mutatoren (Ereignisbehandlungsmethoden wie start() und stopp()) durch update() ersetzt; sie enthalten daher nur Informatoren (wie getUhrzeit() und getFrequenz()), um Daten herauslesen zu können. Daher entfällt IView gänzlich und IModel ist kürzer:

public interface IModel extends Observer { // IModel.java

   static class Uhrzeit { … }

   Uhrzeit getUhrzeit();

   long getFrequenz(); }

Neu im Model ist nur die Observer-Implementierung, die Reaktion auf die Ereignisse:

@Override // Observer // Model.java

public void update(Observable o, Object arg) {

   if (arg.getClass().equals(String.class)) // hässlich

     switch ((String)arg) {

        case "Tick": tick(); break; // uhrzeit wird aktualisiert

        case "Ein": uhrAn = true; break;

        case "Aus": uhrAn = false; break;

        case "Um": frequenz = frequenz == FREQUENZ ? FREQUENZ_UM : FREQUENZ; } }

In der Thread-Schleife von Control muss demnach ein "Tick"-Ereignis ausgelöst werden:

while (true) { // Control.java

   Thread.sleep(model.getFrequenz());

   setChanged(); notifyObservers("Tick"); }

Generische Observer

Einführung Inhaltsverzeichnis 2. Darstellung von Variablen und Werten


Version: 19. April 2012

© Prof. Solymosi, 20102 Beuth-Hochschule für Technik Berlin, Fachbereich VI (Informatik und Medien)

solymosibht-berlin.de