Beobachter und beobachtbar

Hier ist das Problem: Sie entwerfen ein Programm, das Daten, die eine dreidimensionale Szene beschreiben, in zwei Dimensionen rendert. Das Programm muss modular aufgebaut sein und mehrere gleichzeitige Ansichten derselben Szene ermöglichen. Jede Ansicht muss in der Lage sein, die Szene von einem anderen Standpunkt aus unter verschiedenen Lichtbedingungen anzuzeigen. Noch wichtiger ist, dass sich die Ansichten selbst aktualisieren müssen, wenn sich ein Teil der zugrunde liegenden Szene ändert.

Keine dieser Anforderungen stellt eine unüberwindliche Programmierherausforderung dar. Wenn der Code, der jede Anforderung behandelt, de novo geschrieben werden müsste, würde dies den Gesamtaufwand erheblich erhöhen. Glücklicherweise wird die Unterstützung für diese Aufgaben bereits von der Java-Klassenbibliothek in Form von Schnittstelle Observerund Klasse Observablebereitgestellt - beide sind teilweise von den Anforderungen der MVC-Architektur inspiriert.

Die Model / View / Controller (MVC) -Architektur

Die Model / View / Controller-Architektur wurde als Teil von Smalltalk eingeführt, einer beliebten objektorientierten Programmiersprache, die von Alan Kay erfunden wurde. MVC wurde entwickelt, um den Programmieraufwand zu reduzieren, der zum Erstellen von Systemen erforderlich ist, die mehrere synchronisierte Präsentationen derselben Daten verwenden. Seine zentralen Merkmale sind, dass das Modell, die Controller und die Ansichten als separate Entitäten behandelt werden und dass Änderungen, die am Modell vorgenommen werden, automatisch in jeder der Ansichten übernommen werden sollten.

Zusätzlich zu dem im obigen ersten Abschnitt beschriebenen Programmbeispiel kann die Modell- / Ansichts- / Controller-Architektur für Projekte wie das folgende verwendet werden:

  • Ein Diagrammpaket, das simultane Balkendiagramm-, Liniendiagramm- und Kreisdiagrammansichten derselben Daten enthält.
  • Ein CAD-System, bei dem Teile des Entwurfs mit unterschiedlichen Vergrößerungen, in unterschiedlichen Fenstern und in unterschiedlichen Maßstäben angezeigt werden können.

Abbildung 1 zeigt die MVC-Architektur in ihrer allgemeinsten Form. Es gibt ein Modell. Mehrere Controller manipulieren das Modell. Mehrere Ansichten zeigen die Daten im Modell an und ändern sich, wenn sich der Status des Modells ändert.

Abbildung 1. Die Modell- / Ansichts- / Controller-Architektur

Vorteile von MVC

Die Modell- / Ansichts- / Controller-Architektur bietet mehrere Vorteile:

  • Es gibt eine klar definierte Trennung zwischen den Komponenten eines Programms - Probleme in jeder Domäne können unabhängig voneinander gelöst werden.
  • Es gibt eine gut definierte API - alles, was die API richtig verwendet, kann entweder das Modell, die Ansicht oder den Controller ersetzen.
  • Die Bindung zwischen dem Modell und der Ansicht ist dynamisch - sie erfolgt zur Laufzeit und nicht zur Kompilierungszeit.

Durch die Integration der MVC-Architektur in ein Design können Teile eines Programms separat entworfen werden (und so konzipiert werden, dass sie ihre Arbeit gut erledigen) und dann zur Laufzeit miteinander verbunden werden. Wenn eine Komponente später als ungeeignet erachtet wird, kann sie ersetzt werden, ohne die anderen Teile zu beeinträchtigen. Vergleichen Sie dieses Szenario mit dem monolithischen Ansatz, der für viele schnelle und schmutzige Java-Programme typisch ist. Oft enthält ein Frame den gesamten Status, verarbeitet alle Ereignisse, führt alle Berechnungen durch und zeigt das Ergebnis an. Daher ist es in allen außer dem einfachsten dieser Systeme nicht trivial, Änderungen nachträglich vorzunehmen.

Teile definieren

Das Modell ist das Objekt, das die Daten im Programm darstellt. Es verwaltet die Daten und führt alle Transformationen für diese Daten durch. Das Modell kennt weder seine Controller noch seine Ansichten spezifisch - es enthält auch keine internen Verweise darauf. Vielmehr übernimmt das System selbst die Verantwortung, Verbindungen zwischen dem Modell und seinen Ansichten aufrechtzuerhalten und die Ansichten zu benachrichtigen, wenn sich das Modell ändert.

Die Ansicht ist das Objekt, das die visuelle Anzeige der vom Modell dargestellten Daten verwaltet. Es erzeugt die visuelle Darstellung des Modellobjekts und zeigt dem Benutzer die Daten an. Es interagiert mit dem Modell über einen Verweis auf das Modellobjekt selbst.

Die Steuerung ist das Objekt, das die Mittel für die Benutzerinteraktion mit den vom Modell dargestellten Daten bereitstellt. Es bietet die Möglichkeit, Änderungen vorzunehmen, entweder an den Informationen im Modell oder am Erscheinungsbild der Ansicht. Es interagiert mit dem Modell über einen Verweis auf das Modellobjekt selbst.

An dieser Stelle könnte ein konkretes Beispiel hilfreich sein. Betrachten Sie als Beispiel das in der Einleitung beschriebene System.

Abbildung 2. Dreidimensionales Visualisierungssystem

Das zentrale Stück des Systems ist das Modell der dreidimensionalen Szene. Das Modell ist eine mathematische Beschreibung der Eckpunkte und Flächen, aus denen die Szene besteht. Die Daten, die jeden Scheitelpunkt oder jede Fläche beschreiben, können geändert werden (möglicherweise als Ergebnis von Benutzereingaben oder eines Szenenverzerrungs- oder Morphing-Algorithmus). Es gibt jedoch keine Vorstellung von Standpunkt, Anzeigemethode (Drahtmodell oder Volumenkörper), Perspektive oder Lichtquelle. Das Modell ist eine reine Darstellung der Elemente, aus denen die Szene besteht.

Der Teil des Programms, der die Daten im Modell in eine grafische Anzeige umwandelt, ist die Ansicht. Die Ansicht verkörpert die tatsächliche Anzeige der Szene. Es ist die grafische Darstellung der Szene aus einem bestimmten Blickwinkel unter bestimmten Lichtbedingungen.

Der Controller weiß, was mit dem Modell getan werden kann, und implementiert die Benutzeroberfläche, über die diese Aktion initiiert werden kann. In diesem Beispiel kann der Benutzer über ein Dateneingabe-Kontrollfeld möglicherweise Scheitelpunkte und Flächen hinzufügen, ändern oder löschen.

Beobachter und beobachtbar

Die Java-Sprache unterstützt die MVC-Architektur mit zwei Klassen:

  • Observer: Jedes Objekt, das benachrichtigt werden möchte, wenn sich der Status eines anderen Objekts ändert.
  • Observable: Jedes Objekt, dessen Zustand von Interesse sein kann und an dem ein anderes Objekt ein Interesse registrieren kann.

Diese beiden Klassen können verwendet werden, um viel mehr als nur die MVC-Architektur zu implementieren. Sie eignen sich für jedes System, bei dem Objekte automatisch über Änderungen in anderen Objekten informiert werden müssen.

In der Regel ist das Modell ein Subtyp von Observableund die Ansicht ist ein Subtyp von Observer. Diese beiden Klassen übernehmen die automatische Benachrichtigungsfunktion von MVC. Sie bieten den Mechanismus, mit dem die Ansichten automatisch über Änderungen im Modell benachrichtigt werden können. Objektreferenzen auf das Modell sowohl in der Steuerung als auch in der Ansicht ermöglichen den Zugriff auf Daten im Modell.

Beobachter- und beobachtbare Funktionen

Das Folgende sind Codelisten für den Beobachter und beobachtbare Funktionen:

Beobachter

  • public void update(Observable obs, Object obj)

    Wird aufgerufen, wenn sich der Status des Observablen geändert hat.

Beobachtbar

  • public void addObserver(Observer obs)

    Fügt der internen Liste der Beobachter einen Beobachter hinzu.

  • public void deleteObserver(Observer obs)

    Löscht einen Beobachter aus der internen Liste der Beobachter.

  • public void deleteObservers()

    Löscht alle Beobachter aus der internen Beobachterliste.

  • public int countObservers()

    Gibt die Anzahl der Beobachter in der internen Liste der Beobachter zurück.

  • protected void setChanged()

    Sets the internal flag that indicates this observable has changed state.

  • protected void clearChanged()

    Clears the internal flag that indicates this observable has changed state.

  • public boolean hasChanged()

    Returns the boolean value true if this observable has changed state.

  • public void notifyObservers()

    Checks the internal flag to see if the observable has changed state and notifies all observers.

  • public void notifyObservers(Object obj)

    Checks the internal flag to see if the observable has changed state and notifies all observers. Passes the object specified in the parameter list to the notify() method of the observer.

Next we'll take a look at how to create a new Observable and Observer class, and how to tie the two together.

Extend an observable

A new class of observable objects is created by extending class Observable. Because class Observable already implements all of the methods necessary to provide the desired behavior, the derived class need only provide some mechanism for adjusting and accessing the internal state of the observable object.

In the ObservableValue listing below, the internal state of the model is captured by the integer n. This value is accessed (and, more importantly, modified) only through public accessors. If the value is changed, the observable object invokes its own setChanged() method to indicate that the state of the model has changed. It then invokes its own notifyObservers() method in order to update all of the registered observers.

Listing 1. ObservableValue

 import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } } 

Implement an observer

A new class of objects that observe the changes in state of another object is created by implementing the Observer interface. The Observer interface requires that an update() method be provided in the new class. The update() method is called whenever the observable changes state and announces this fact by calling its notifyObservers() method. The observer should then interrogate the observable object to determine its new state, and, in the case of the MVC architecture, adjust its view appropriately.

In the following TextObserver listing, the notify() method first checks to ensure that the observable that has announced an update is the observable that this observer is observing. If it is, it then reads the observable's state, and prints the new value.

Listing 2. TextObserver

 import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } } 

Tie the two together

A program notifies an observable object that an observer wishes to be notified about changes in its state by calling the observable object's addObserver() method. The addObserver() method adds the observer to the internal list of observers that should be notified if the state of the observable changes.

The example below, showing class Main, demonstrates how to use the addObserver() method to add an instance of the TextObserver class (Listing 2) to the observable list maintained by the ObservableValue class (Listing 1).

Listing 3. addObserver()

 public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } } 

How it all works together

The following sequence of events describes how the interaction between an observable and an observer typically occurs within a program.

  1. First the user manipulates a user interface component representing a controller. The controller makes a change to the model via a public accessor method -- which is setValue() in the example above.
  2. The public accessor method modifies the private data, adjusts the internal state of the model, and calls its setChanged() method to indicate that its state has changed. It then calls notifyObservers() to notify the observers that it has changed. The call to notifyObservers() could also be performed elsewhere, such as in an update loop running in another thread.
  3. The update() methods on each of the observers are called, indicating that a change in state has occurred. The observers access the model's data via the model's public accessor methods and update their respective views.

Observer/Observable in an MVC architecture

Now let's consider an example demonstrating how observables and observers typically work together in an MVC architecture. Like the model in the ObservableValue (Listing 1) the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in ObservableValue. The code for the model is found here.

Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here.

By designing the system using the MVC architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here.

About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing.

This story, "Observer and Observable" was originally published by JavaWorld .