Modell-Ansicht-Bediener

(Model-View-Controller)

von Andreas Krutscher, WI6443

 [ Seminarübersicht] ... [Architekturmuster] ... [Literatur] ... [ > ] 


Name

Modell-Ansicht-Bediener (Model-View-Controller)

Das Modell-Ansicht-Bediener Architekturmuster (MVC) teilt eine interaktive Applikation in drei Komponenten. Das Modell enthält die Kernfunktionalität und die Daten. Ansichten stellen Informationen für den Benutzer dar. Bediener verarbeiten Benutzereingaben. Ansichten und Bediener bilden zusammen die Schnittstelle für den Benutzer. Eine Benachrichtigungs-Strategie stellt die Konsistenz zwischen der Benutzungsschnittstelle und dem Modell sicher.


Auch bekannt als

Ähnlichkeiten finden sich zum Beobachter-Muster von Erich Gamma e.a.


Beispiel

Betrachten wir ein Informationssystem für politische Wahlen. Ein solches System bietet eine Tabelle, in der Daten eingegeben werden können und verschiedene Arten von Tabellen und Grafiken, die das aktuelle Wahlergebnis darstellen. Der Benutzer kann in das System über eine graphische Schnittstelle eingreifen. Alle dargestellten Informationen müssen Änderungen des Wahlergebnisses sofort wiedergeben.
Es soll möglich sein, neue Arten der Präsentation wie zum Beispiel eine graphische Darstellung der Aufteilung der Sitze im Parlament ohne große Eingriffe in das System hinzuzufügen. Das System soll auf verschiedene Plattformen mit verschiedenen "Look & feel"-Standards portierbar sein.


Kontext

Interaktive Applikationen mit einer flexiblen Mensch-Computer Schnittstelle.


Problemstellung

Benutzerschnittstellen sind bezüglich Änderungen am System besonders empfindlich. Wenn die Funktionalität einer Applikation erweitert wurde, muß z.B. auch das Hauptmenü um erweitert werden, um die neue Funktion nutzen zu können. Verschiedene Benutzer wollen vielleicht sogar verschiedene Schnittstellen verwenden, und auf verschiedenen Plattformen sieht die Applikation unterschiedlich aus. Sogar das Installieren einer neuen Version des benutzen Fenstersystems kann Codeänderungen nach sich ziehen.
Ein System mit dieser geforderten Flexibilität zu bauen ist teuer und fehleranfällig, wenn die Benutzerschnittstelle mit dem Funktionalen Kern der Software fest verwoben ist.

Die folgenden Kräfte beeinflussen eine Lösung dieses Problems:


Lösung

Ein Model-View-Controller (MCV) wurde das erste mal in der Smalltalk-80 Programmierumgebung eingeführt. Ein MCV teilt eine Applikation in drei Bereiche: Verarbeitung, Ausgabe und Eingabe.
Die Modell-Komponente kapselt die Kern-Daten und -Funktionalität. Sie ist völlig unabhängig von speziellen Ausgabe-Repräsentationen oder Eingabeverhalten.
Ansicht-Komponenten geben Informationen an den Benutzer aus. Eine Ansicht bezieht seine Daten von dem Modell. An ein Modell können viele Ansicht-Komponenten gebunden sein.
Jede Ansicht hat eine zugehörige Bediener-Komponente. Bediener-Komponenten erhalten Eingaben, gewöhnlicherweise über Ereignisse die Daten bezüglich Mausbewegungen, Mausereignissen oder Tastatureingaben enthalten. Solche Ereignisse wandelt die Bediener-Komponente dann in Anfragen an die Ansicht oder das Modell um. Der Benutzer arbeitet mit dem System ausschließlich über Bediener-Komponenten.
Die Trennung des Modells von Ansichten und Bediener-Komponenten erlaubt mehrere Ansichten von dem selben Modell. Wenn der Benutzer die Daten des Modells über eine Bediener-Komponente manipuliert, dann sollen alle anderen Ansichten und Bediener-Komponenten die Änderungen sofort wiedergeben. Dazu muß das Modell alle angemeldeten Ansichten darüber benachrichtigen, wenn sich seine Daten geändert haben. Die Ansichten beziehen bei Erhalt der Benachrichtigung die Daten neu vom Modell und aktualisieren ihre Ausgabe. Dieser Änderungs-Fortpflanzungs Mechanismus wird im Darstellung-Abstraktion-Kontrolle (PAC) Muster beschrieben.


Struktur

Modell

Wie bereits festgestellt, stellt die Modell-Komponente den funktionalen Kern der Applikation dar. Sie kapselt geeignete Daten und bietet Prozeduren, die auf den Daten applikationsspezifische Aufgaben ausführen. Bediener-Komponenten rufen diese Prozeduren in gemäß Benutzervorgabe auf. Das Modell bietet außerdem Funktionen, die von den Ansicht-Komponenten aufgerufen werden, um Daten zum Anzeigen abzurufen.
Für den Änderungs-Fortpflanzungs Mechanismus wird im Modell eine Registrierung der abhängigen Ansichten und Bediener benötigt. Alle Ansichten und ebenso einige Bediener registrieren ihren Bedarf auf Benachrichtigung. Änderungen an den Daten des Modells lösen dann die Benachrichtigung aller registrierten Komponenten aus. Diese Registrierung ist die einzige Verbindung zwischen dem Modell und den Ansichten und Bediener-Komponenten.
Modell Mitarbeiter 
  • Ansicht
  • Bediener
  • Verantwortlichkeit 
  • Bildet den funktionalen Kern der Applikation
  • Registriert abhängige Ansichten und Bediener-Komponenten
  • Benachrichtigt abhängige Komponenten über Datenänderungen
  • Ansicht

    Jede Ansicht definiert eine Update-Prozedur, die im Zuge einer Benachrichtigung durch das Modell aufgerufen wird. Innerhalb dieser Prozedur wird eine Ansicht seine dargestellten Daten neu vom Modell beziehen und neu darstellen.
    Bei der Instantiierung einer Ansicht-Klasse wird dem Konstruktor eine Referenz auf ein Modell mitgegeben, bei dem sich das neue Ansicht-Objekt bereits während der Initialisierung anmeldet. Jedes Ansicht-Objekt kann zusätzlich genau eine Bediener-Komponente erzeugen. Bei dessen Initialisierung wird ebenfalls eine Referenz auf das Modell mitgegeben, damit sich auch der Bediener beim Modell registrieren kann. Weiterhin übergibt das Ansicht-Objekt dem Bediener eine Referenz auf sich selbst. Dadurch kann der Benutzer über das Bediener-Objekt die Art der Darstellung in der Ansicht verändern. Änderungen wie z.B. das Scrollen eines Ansicht-Bereiches betreffen nicht das Modell. Deswegen ist es sinnvoll, daß das Bediener-Objekt direkt auf das Ansicht-Objekt Zugriff hat.
    Ansicht Mitarbeiter 
  • Bediener
  • Modell
  • Verantwortlichkeit 
  • Erzeugt und initialisiert seine zugehörige Bediener-Komponente
  • Präsentiert Informationen für den Benutzer
  • Implementiert eine Update-Prozedur
  • Bezieht Daten vom Modell
  • Bediener

    In welcher Weise das Bediener-Objekt seine Ereignisse erhält hängt im wesentlichen vom Betriebssystem und der Programmierumgebung ab. Aus Vereinfachungsgründen wird angenommen, daß die Bediener-Klasse Ereignisbehandlungsroutinen für alle relevanten Ereignisse definiert.
    Wenn das Verhalten des Bedienerobjektes vom Zustand des Modells abhängt, muß es sich beim Modell registrieren und - wie eine Ansicht - eine Update-Prozedur anbieten. So kann es bei einer Änderung des Zustandes des Modell sein eigenes Erscheinungsbild den neuen Bedürfnissen anpassen. Das ist beispielsweise sinnvoll, wenn bei bestimmten Zuständen Menüeinträge gesperrt werden sollen.
    Bediener Mitarbeiter 
  • Ansicht
  • Modell
  • Verantwortlichkeit 
  • Nimmt Benutzereingaben in Form von Ereignissen entgegen
  • Transformiert Ereignisse in Serviceaufrufe des Modells oder in Anfragen an die Ansicht
  • Implementiert eine Update-Prozedur, wenn nötig.
  • Objektorientierte Implementierung

    Eine objektorientierte Implementierung des MVC wird für jede Komponente eine eigene Klasse definieren. Die Ansicht- und Bedienerklasse werden ein gemeinsames Elternobjekt besitzen, daß die Update-Prozedur definiert. Dies ist in folgendem Diagramm dargestellt.
    In unserem Beispielsystem hält das Modell die kumulierten Stimmen für jede politische Partei und erlaubt Ansicht-Objekten, diese Zahlen abzufragen. Es veröffentlicht weiterhin Prozeduren zum Manipulieren der Daten an die Bedienerobjekte.


    Interaktionen

    Die folgenden Szenarien betreffen das dynamische Verhalten eines Modell-Ansicht-Bediener Systems. Aus Vereinfachungsgründen ist immer nur ein Ansicht-Bediener Paar dargestellt.

    Szenario I: Auslösen des Benachrichtigungs-Mechanismus

    Der Bediener nimmt Benutzereingaben in seiner Ereignisbehandlungsroutine entgegen, interpretiert das Ereignis und ruft den entsprechenden Dienst des Modells auf. Das Modell führt diese Dienst aus, was unter Umständen zu Veränderungen der internen Daten führt. Ist das der Fall, benachrichtigt das Modell alle registrierten Ansichten und Bediener indem es deren aktualisiere()-Prozeduren aufruft. Jede benachrichtigte Ansicht ruft die Daten vom Modell ab und aktualisiert seine Darstellung auf dem Bildschirm. Bediener rufen die Daten erneut ab, um ihre Bedienelemente dem neuen Datenzustand anzupassen. Schlußendlich erhält der auslösende Bediener die Kontrolle zurück und beendet die Ereignisbehandlung.

    Szenario II: Initialisierung des MVC

    Als erstes wird das Modell instantiiert, welches selbst seine internen Daten initialisiert. Als nächstes wird eine Ansicht erzeugt. Bei seiner Initialisierung wird ihr eine Referenz auf das Modell mitgegeben. Die Initialisierung kann aber auch schon mit Hilfe des Konstruktors bei der Instantiierung geschehen. Nach der Initialisierung meldet sich die neue Ansicht beim Modell an. Die Initialisierung wird fortgesetzt indem die neue Ansicht eine Bediener-Komponente erzeugt. Dem Bediener werden bei der Initialisierung Referenzen auf das Modell und auf die Ansicht mitgegeben. Bei der Initialisierung registriert sich auch der Bediener bei dem Modell. Nach der Initialisierung startet die Applikation ihre eigene Ereignisbehandlung.


    Implementierung

    Die Schritte 1 bis 6 sind fundamental wichtig, wenn man ein MVC erstellen möchte. Alle weiteren Schritte beschreiben zusätzliche Überlegungen, die das System noch flexibler machen.

    1. Trenne Mensch-Computer Interaktionen von der Kern-Funktionalität

    Analysiere die Applikations-Domäne und trenne die Kern-Funktionalität vom Ein- und Ausgabeverhalten heraus. Entwerfe die Modell-Komponente und kapsele in ihr die Daten und die Funktionalitäten, die mit ihnen verbunden sind. Weiterhin werden Funktionen benötigt, um auf die Daten zuzugreifen und um die Funktionalitäten zu nutzen. Mache diese Funktionen in einer Schnittstelle verfügbar.

    2. Implementiere den Änderungs-Fortpflanzungs Mechanismus

    Die Modell-Klasse muß um eine Registrierung erweitert werden, in der die Referenzen auf die beobachtenden Objekte gehalten werden. Es müssen Prozeduren zur Verfügung gestellt werden, es Ansichten und Bedienern erlauben sich an- und abzumelden. Die benachrichtige()-Prozedur des Modells benachrichtigt alle angemeldeten Beobachter. Alle Prozeduren, die den internen Zustand des Modells verändern müssen nach Durchführung der Änderungen die benachrichtige()-Prozedur aufrufen.
    Wie eine Implementierung dessen aussehen kann zeigt folgender Beispielcode.

    3. Entwerfe und implementiere die Ansichten

    Jetzt muß das Erscheinungsbild der Ansichten entworfen werden. Jede Ansicht kann z.B. über eine draw() Prozedur verfügen, die die benötigten Daten vom Modell abruft und auf dem Bildschirm ausgibt. Die Bildschirmausgabe hängt hauptsächlich von der Benutzerschnittstelle und der Systemumgebung und nicht zuletzt von dem Informationsbedürfnis des Benutzers ab.
    Alle verschiedene Ansicht-Klassen werden von Beobachter abgeleitet und die aktualisiere() Prozedur mit einer passenden Implementierung überschreiben. Die einfachste Art der Implementierung ist, innerhalb von aktualisiere() die Prozedur draw() aufzurufen. Für komplexe Systeme kann diese simple Implementierung jedoch ineffizient sein. In solchen Fällen muß man andere Überlegungen anstellen und andere Wege beschreiten. Eine Möglichkeit ist, Ansichten bei der Benachrichtigung einen Parameter mit zu übergeben, der Auskunft darüber gibt, was sich innerhalb der Daten verändert hat. Damit kann eine Ansicht entscheiden, ob eine Aktualisierung überhaupt notwendig ist.
    Unabhängig davon braucht jede Ansicht-Klasse eine Initialisierung, innerhalb derer sich das Objekt beim Modell anmeldet.

    4. Entwerfe und implementiere die Bediener

    Für jede Ansicht innerhalb der Applikation muß daß Verhalten des Systems in Bezug auf Benutzereingaben spezifiziert werden. Wir setzen dabei voraus, daß das zugrunde liegende Betriebssystem die nötigen Geschehnisse der Applikation in Form von Events mitteilt. Eine Bediener-Komponente muß nun nur noch diese Ereignisse entgegennehmen und interpretieren.
    Innerhalb der Initialisierung eines Bedieners wird es an das Modell und an eine Ansicht gebunden. Wie danach die Ereignisbehandlung gestartet wird hängt wiederum von der Systemumgebung und der verwendeten Programmiersprache ab.
    Der beispielhafte Quelltext zeigt eine abstrakte Bediener-Klasse, bei der die bearbeiteEreignis()-Prozedur in Unterklassen überschrieben werden muß. Weiterhin sind natürlich noch Methoden nötig, die die Daten des Modells modifizieren. Der Aufruf solcher Methoden erfolgt aus der bearbeiteEreignis(Event e)-Prozedur heraus.

    5. Entwerfe und implementiere die Ansicht-Bediener Beziehung

    Eine Ansicht erzeugt ihren zugehörigen Bediener typischerweise während der Initialisierung. Es ist allerdings besser, die Erzeugung des Bedieners in eine Fabrikmethode, z.B. Bediener erzeugeBediener() zu kapseln. So haben Unterklassen der Ansicht-Klasse die Möglichkeit, gezielt die Erzeugung des Standard-Bedieners gegen einen anderen Bediener auszutauschen indem sie die Fabrikmethode überschreiben.

    6. Implementiere die Initialisierung des MVC

    Die Initialisierung des Systems geschieht in der in Szenario II dargestellten Reihenfolge. Nach der Initialisierung wird das System in eine Ereignisbehandlungs-Routine übergehen.
    Die Initialisierung des oben dargestellten Beispielcodes könnte wie folgt aussehen. Diese Programmzeilen sind Teil eines übergeordneten Programmabschnittes:

    7. Dynamische Ansichten-Erzeugung

    Soll die Applikation das dynamische Öffnen und Schließen von Ansichten erlauben, ist es sinnvoll, eine weitere Komponente zum Verwalten der Ansichten zu erstellen.

    8. Austauschbare Bediener

    Die Trennung des Bedieners von der Ansicht ermöglicht es, für eine Ansicht verschiedene Arten von Bedienern zur Verfügung zu stellen. Das ermöglicht es uns, Benutzern mit verschiedenen Fähigkeiten verschiedene Bediener-Komponenten anzubieten. Ein erfahrener Benutzer wird eine umfangreichere Benutzerschnittstelle verwenden wollen als ein Benutzer, der zum ersten mal mit dem System arbeitet. Somit ist es auch möglich, bestimmten Benutzergruppen ausschließlich lesenden Zugriff auf die Daten zu gewähren.

    9. Infrastruktur für hierarchische Ansichten und Bediener

    Eine MVC-Komponente wie z.B. eine Ansicht kann selbst wiederum aus einer vielzahl von wiederverwendbaren Ansichten oder Bedienern bestehen. Dies sind im allgemeinen Elemente, die häufig verwendet werden, also Pushbuttons, Edit-Felder, Menüs usw. Die Benutzerschnittstelle wird erstellt, indem vorhandene Komponenten verwendet und zusammengefügt werden. Dieses Zusammensetzen wird im Kompositum-Muster (E. Gamma, e.a.) näher betrachtet.

    10. Weitere Trennung von Systemspezifischen Details

    Ein solches komplexes MVC-System zu entwickeln kann unter Umständen sehr teuer sein und es wird gewünscht, das System auf mehreren Plattformen anzubieten. In einem solchen Fall ist es sinnvoll, weitere Abstraktionsschritte durchzuführen und die MVC-System-Details von den plattformabhängigen zu trennen indem sie in getrennten Klassen definiert werden. Diese müssen dann über eine definierte plattformneutrale Schnittstelle miteinander kommunizieren.


    Varianten

    Dokument-Ansicht

    Diese Variante entspannt die Trennung zwischen Ansicht und Bediener. In vielen GUI Plattformen ist die Bildschirmausgabe stark mit der Ereignisbehandlung verwoben. Man kann die Ansicht- mit der Bedienerkomponente verschmelzen. Dann muß man allerdings hinnehmen, daß die Bedienung der Ansicht nicht mehr austauschbar ist. Dabei ist es nach wie vor möglich, mehrere Ansichten eines Dokumentes zu erzeugen.


    Bekannte Verwendung

    Smalltalk ist mit seinem Benutzer-Schnittstellen Framework das am besten bekannte Beispiel für eine Implementierung des MVC-Musters.

    MFC. In der Visual C++ Entwicklungsumgebung ist die Dokument-Ansicht Variante des MVC-Musters integriert.

    ET++ implementiert ebenfalls die Dokument-Ansicht Variante des MVC-Musters. Eine typische ET++-basierte Applikation implementiert seine eigene Dokument-Klasse und eine zugehörige Ansicht-Klasse.


    Konsequenzen

    Vorteile:

    Konsequenzen:


    Verwandte Muster

    Das Darstellung-Abstraktion-Kontrolle (PAC) Muster wählt einen anderen Ansatz, um die Benutzerschnittstelle vom funktionalen Kern der Applikation zu trennen. [BMR96]

    [ Seminarübersicht] ... [Architekturmuster] ... [Literatur] ... [ > ]