© saicle/Shutterstock.com
Model View Controller in Webanwendungen

Was nicht passt, wird passend gemacht


Model View Controller ist eines der bekanntesten Entwurfsmuster. Allerdings ist es mit über dreißig Jahren deutlich älter als das World Wide Web. Wen wundert es da, wenn der Einsatz von MVC im Web das eine oder andere Fragezeichen aufwirft? Wir sehen einmal ganz genau hin.

In der letzten Ausgabe haben wir damit begonnen, einmal „hinter die Kulissen“ von MVC zu schauen. Der zentrale Nutzen von MVC ist „Separate Presentation“, also die Abtrennung der Präsentation von den anderen Belangen wie Logik oder gar Datenhaltung [1], [2]. Die Grundidee dabei ist es, Daten in einem so genannten Model zentral zu repräsentieren, damit verschiedene Ansichten (Views) auf diese Daten synchronisiert werden können. Ein klassisches Beispiel dafür sind Multiple Document-Interface-Applikationen. Sie ersparen es dem Benutzer, wiederholt zwischen zwei Fenstern hin und her zu blättern, denn er kann mehrere Fenster gleichzeitig öffnen. Damit das sinnvoll funktioniert, müssen die unterschiedlichen Views (GUI-Elemente) „automatisch“ jeweils dann aktualisiert werden, wenn sich das Model geändert hat. Hierbei kommt eine Subject-Observer-Beziehung zum Einsatz, die wir in der letzten Ausgabe bereits ausführlich diskutiert haben [3].

In dieser Ausgabe wollen wir uns mit MVC im Web beschäftigen. Bevor wir uns näher ansehen, wie das überhaupt funktionieren kann, sehen wir uns noch eine „echte“ MVC-Implementierung in PHP an. Wir stellen uns vor, dass unser Model einen Zähler repräsentiert und es zwei Buttons – also Views – gibt, die den Zähler inkrementieren. Jede View zeigt zudem den aktuellen Zählerstand an. Klickt man also auf einen der beiden Buttons, muss der Zähler im Model inkrementiert werden und sich daraufhin beide Views mit dem Model synchroniseren. Als gute Entwickler definieren wir im ersten Schritt die notwendigen Schnittstellen (Listing 1). In Listing 2 implementieren wir zuerst das Model.

Listing 1

interface Model { public function attach(View $view); } interface View { public function attach(Controller $controller); public function update(Model $model); } interface Controller { public function update(View $view); }

Listing 2

class MvcModel implements Model { private $views = array(); private $counter = 0; public function setCounter($counter) { $this->counter = $counter; $this->notify(); } public function getCounter() { return $this->counter; } public function attach(View $view) { $this->views[] = $view; } private function notify() { foreach ($this->views as $view) { $view->update($this); } } }

Das zentrale Feature des Models ist, dass es Daten hält, in unserem Fall den Zähler. Es gibt einen Getter für den Zähler und einen Setter, der zugleich die private Methode notify() aufruft, um allen angehängten Views Bescheid zu sagen, dass sich das Model geändert hat – beziehungsweise haben könnte.

Um die Views als Beobachter anzuhängen, braucht das Model natürlich eine Methode attach(). Die private Methode notify() schließlich sorgt dafür, dass bei einer Zustandsänderung des Models alle angehängten Views benachrichtigt werden. Dabei wird mit $this jeweils eine Referenz auf das Model an die Views übergeben, damit diese den Zustand des Models abrufen können. Nun kommt die View an die Reihe (Listing 3).

Listing 3

class MvcView implements View { private $controller; private $counter; public function attach(Controller $controller) { $this->controller = $controller; } public function update(Model $model) { $this->counter = $model->getCounter(); } public function getCounter() { return $this->counter; } public function render() { return 'Counter: ' . $this->counter; } public function notify() { $this->controller->update($this); } }

Die View bietet mit der Methode attach() zunächst die Möglichkeit, einen Controller anzuhängen. Theoretisch könnten das auch mehrere Controller sein, wenn auf verschiedene Events in der View unterschiedlich reagiert werden oder diese Interaktion Auswirkungen auf mehrere Models haben muss.

Neben der obligatorischen update()-Methode, um der View zu sagen, dass sie sich aktualisierte Daten vom Model holen soll, gibt es noch die Methode getCounter(), über die sich der Controller die Daten von der View holen kann. Um die View darzustellen, brauchen wir natürlich eine render()-Methode, die in unserem Fall einfach nur einen String zurückgibt.

Die Methode notify() ist in der View öffentlich, um den Controller direkt bei einer Zustandsänderung beziehungsweise einem Event wie „Button geklickt“ zu benachrichtigen. Nun fehlt noch der Controller (Listing 4).

Listing 4

class MvcController implements Controller { private $model; public function __construct(Model $model) { $this->model = $model; } public function update(View $view) { $this->model->setCounter($view->getCounter() + 1); } }

Der Controller ist fest mit einem Model verbunden, daher muss dieses als Konstruktorparameter übergeben werden. Sobald der Controller von der View benachrichtigt wird, dass es Arbeit gibt, holt er sich den Zustand (also den Zählerstand) von der View, indem er die Methode getCounter() aufruft. Diesen Zählerstand inkrementiert er und setzt den neuen Zählerstand im Model. Man könnte den Code noch etwas schöner machen, indem man im Model eine Methode increment() implementiert, die der Controller aufruft. Dann allerdings müsste der Controller unterscheiden, welches Event die View gemeldet hat: ein Mouse-over-Event beispielsweise soll ja nicht ein Inkrementieren des Zählers auslösen. In der aktuellen Implementierung ist es egal, wegen welchem Event die View den Controller benachrichtigt, er holt sich immer den aktuellen Zählerstand, und wenn dieser unverändert ist, dann machen wir uns zwar unnötig Arbeit, aber das Ergebnis ist in jedem Fall richtig. Diese überflüssige Arbeit könnte man verhindern, in dem das Model nur bei einer „echten“ Zustandsänderung die notify()-Methode aufruft:

public function setCounter($counter) { $previous = $this->counter; $this->counter = $counter; if ($previous != $counter) { $this->notify(); } }

Nun wollen wir mal sehen, ob unser MVC auch richtig funktioniert. Im nachfolgenden Steuerprogramm werden zunächst die Objekte erzeugt und miteinander verknüpft. Dann initialisieren wir das Model mit dem Zählerstand 1 und stellen die beiden Views dar, um uns davon zu überzeugen, dass sie den Zählerstand auch wirklich korrekt anzeigen. Danach simulieren wir durch den Aufruf der Methode notify() einen Klick auf den Button, der durch die erste View repräsentiert wird, und stellen erneut beide Views dar, um zu sehen, ob alles korrekt funktioniert hat (Listing 5).

Listing 5

$model = new MvcModel; $controller1 = new MvcController($model); $view1 = new MvcView; $view2 = new MvcView; $controller2 = new MvcController($model); $model->attach($view1); $model->attach($view2); $view1->attach($controller1); $view2->attach($controller2); $model->setCounter(1); var_dump($view1->render()); var_dump($view2->render()); $view1->notify(); var_dump($view1->render()); var_dump($view2->render());

Das Programm erzeugt folgende Ausgabe – und wir sind glücklich:

string(10) "Counter: 1" string(10) "Counter: 1" string(10) "Counter: 2" string(10) "Counter: 2"

Leider währt das Glück nur kurz, denn wir müssen un...

Neugierig geworden?

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang