Start - Publikationen - Wissen - TOGAF - Impressum -

Model View Control - Basiskonzepte


Um wirklich zu verstehen was das Model View Control (MVC) Paradigma bedeutet, muss man sich in die Rolle der GUI Frameworkentwickler versetzen. Diese entwickeln die GUI Komponenten mit denen dann später eine Anwendung entwickelt wird. Die GUI Komponenten dieses Frameworks haben dabei zwei wesentliche Eigenschaften:

  1. Über diese Komponenten initiiert der Nutzer Aktionen mit denen der Programmablauf der GUI gesteuert wird.
  2. Den Komponenten werden Inhalte zugeordnet, diese Inhalte werden von den Komponenten dargestellt. Dazu abstrahieren die Komponenten die Daten, die sie darstellen können. Diese Abstraktionen werden als Viewmodel-Interfaces definiert und sind Bestandteil des Frameworks.
Zum Zeitpunkt der Entwicklung von GUI Frameworks steht weder fest, wie die Aktionen im Detail konsumiert werden, noch welche Daten darzustellen sind. Beides bestimmt der konkrete Anwendungsfall. Dieses grundsätzliche Problem löst der GUI Frameworkentwickler also mittels geeigneter Design-Patterns und stellt eine Infrastruktur bereit, die man, fertig implementiert, folgendermaßen charakterisieren kann:
  • Die view besteht aus den verwendeten GUI Komponenten und spezifischen Implementierungen ihrer Viewmodel-Interfaces. Ein Beispiel dafür ist der JTree mit Implementierungen seiner Viewmodel-Interfaces wie das TreeModel.
  • Der control ist der Code der Ablaufsteuerung. Man findet diesen Code in static void main und in den Implementierungen der ActionListener, die bei den GUI Komponenten angemeldet werden. Ein wichtiger Teil des control betrifft das Data Binding, das view und model miteinander verknüpft.
  • Das model besteht aus beliebig strukturierten Fachdaten. Für die Reaktion auf Änderungen der Fachdaten gestattet das model die Anmeldung von Model-Listener. Sowohl in der view (in den Implementierungen der Viewmodel-Interfaces) als auch im control werden solche Model-Listener dann implementiert und melden sich beim model an.
MVC Bestandteile
view 1. GUI Komponenten (wie JTree)
2. Viewmodell-Implementierungen ihrer Viewmodell-Interfaces (wie TreeModel), die Viewmodell-Implementierungen sind über Model-Listener beim model angemeldet
control 1. Code in static void main(String[] args)
2. Code in Action-Listenern (angemeldet im view)
3. Code in Model-Listener (angemeldet im model)
model Fachdaten und Schnittstellen zum Anmelden/Abmelden von Model-Listener Implementierungen

MVC vermeidet zyklische Abhängigkeiten


Wenn das MVC-Paradigma konsequent implementiert ist, werden zwischen model, view und control ausschließlich folgende direkten Abhängigkeiten realisiert:

view  <--------- control
  |                 |
  |                 |
  +--->  model  <---+

Alle entgegengesetzt gerichteten (direkten) Abhängigkeiten sind komplett über das Observer-Pattern vermieden. Aus der Sicht der view ist control ein Listener für Action-Events, aus der Sicht des model sind view und control Listener für Model-Events:

      action events
view  -------------> control ist 
      model events   action listener
model -------------> control und view 
                     sind model listener

Diese Grafik unterstreicht noch einmal die zentrale Motivation für MVC: das model ist komplett unabhängig von allen anderen Bestandteilen der GUI und könnte auch unabhängig von view und control genutzt werden (zum Beispiel für Unit-Tests).

Implementierung von view und control


Wie beschrieben werden im view hauptsächlich die Implementierungen der beteiligten Viewmodells bereit gestellt. Dabei ist vor allem auf diese Dinge zu achten:

  • Die Objekte in den Viewmodells sind möglichst typsicher zu implementieren. Wenn Personen-Objekte in einem JTree dargestellt werden sollen, dann sind diese auch im TreeModel als solche zu berücksichtigen. So gibt beispielsweise TreeModel#getChild Objekte vom Typ Person zurück.
  • Die Objekte, die in den Viewmodells verwendet werden, müssen die Standard-Methoden equals, hashCode und gegebenenfalls compareTo konsistent implementieren.
  • Die Listener Anmeldungen und die Benachrichtigung der Listener in den Viewmodells muss berücksichtigt werden. Nur dann werden sich die betroffenen view-Komponenten korrekt aktualisieren. So löst ein JTree#setModel immer auch ein addTreeModelListener im angemeldeten TreeModel aus. Wem das zu kompliziert erscheint, kann sich einer Default-Implementierung bedienen, bei der nur die Objekt-bezogenen Methoden korrekt implementiert werden müssen.
  • Über Änderungen in den Fachdaten lassen sich die Viewmodells informieren, indem sie sich als Listener im model anmelden.
Beim control kann man eigentlich nicht viel falsch machen, denn es dürfen direkte Abhängigkeiten zum view und zum model "verbaut" werden. Das Anmelden von Action-Listenern am view ist überdies die einzig praktikable Lösung, control-Code in den GUI-Komponenten zu verdrahten, dabei kommen oft anonyme innere Klassen zum Einsatz. Änderungen in den Fachdaten werden ebenfalls über das Anmelden von Listener im model realisiert.

Implementierung des model


Wie beschrieben erfolgt die Benachrichtigung bei Änderungen im model über Model-Listener, die von den Viewmodel-Interfaces der GUI Komponente und vom control implementiert werden. Geeignete Interfaces für diese Model-Listener sind somit zwingender Bestandteil des model-Designs und unvermeidlich, wenn das MVC-Designpattern umgesetzt werden soll. Kein GUI Framework kann das Design dieser Model-Listener vorwegnehmen, denn es ist spezifisch für die Struktur der Fachdaten der umzusetzenden Anwendung.

                       +----------+
view implements    --> |  Fach    | 
 IModelListener        |  Daten   |
                       |    +     |
control implements --> |  IModel  |
 IModelListener        | Listener |
                       +----------+

In einem ersten Entwurf kann dabei einfach ein "generisches" Model-Listener-Interface definiert werden:

interface IModelListener {
  //
  void contentChanged();
}
Implementierungen können im model angemeldet (und natürlich auch abgemeldet) werden:
private final List<IModelListener> listenerList = new LinkedList<IModelListener>(); 
// Anmelden
void addModelListener(IModelListener listener) {
  //
  synchronized(listenerList) {
    if (!listenerList.contains(listener) {
      listenerList.add(listener);
    }
  }
}
// Abmelden
void removeModelListener(IModelListener listener) {
  //
  synchronized(listenerList) {
    listenerList.remove(listener);
  }
}
// Benachrichtigen: wird immer dann gerufen,
// wenn sich im model etwas ändert
private void notify() {
  synchronized(listenerList) {
    for (IModelListener listener : listenerList) {
      listener.contentChanged();
    }
  }
}
Im Verlauf der Entwicklung kann dann dieses "generische" Model-Listener-Interface ausdifferenziert werden. Das bedeutet, dass spezialisierte Model-Listener-Interfaces für die verschiedenen Bereiche der Fachdaten definiert werden. Weiterhin könnten die Modelländerungen auch in contentChanged mit übergeben werden. Das alles sind spezifische Designentscheidungen und können nicht vorweg genommen werden.

MVC und Paketdesign


Abhängigkeiten werden über import Statements in den beteiligten Klassen kontrolliert. Damit liegt auf der Hand, dass die verschiedenen Codeteile von model, view und control einer fachlichen Komponente auf unterschiedliche Pakete zu verteilen sind. Im einfachsten Fall sind das drei für model, view und control, komplexere fachliche Komponente können dann noch weiter unterteilen. MVC ist korrekt implementiert, wenn gilt:

  • in den model-Paketen fehlen Imports von view- und control-Klassen
  • in den view-Paketen fehlen Imports von control-Klassen
Es gibt dann mit Sicherheit keine zyklischen Abhängigkeiten zwischen diesen Paketen.

Fazit


Das MCV-Designpattern verhindert Abhängigkeiten vom model zum view und control und vom view zum control in einer Anwendung. Dadurch werden zyklische Abhängigkeiten zwischen model, view und control vermieden. Das model, verantwortlich für die Verwaltung von fachlichen Daten, kann dann unabhängig vom Rest der Anwendung entwickelt, genutzt und insbesondere leicht automatisiert getestet werden. Die Grundlage der Trennung ist die korrekte Implementierung der Viewmodel-Listener, die vom GUI Framework vorgegeben werden, sowie eine Benachrichtigung bei Änderungen im model über geeignete Model-Listener. Die Model-Listener sind damit zwingender Bestandteil eines MVC-konformen models.

Korrekt implementiertes MCV-Designpattern ist die Grundlage für eine funktionierende Implementierung bei nebenläufiger Abarbeitung in der GUI.

Referenzen


A Swing Architecture Overview

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved