Android-Architektur-Muster Teil 1: Model-View-Controller

Florina Muntenescu
Florina Muntenescu

Follow

Nov 1, 2016 – 6 min read

Vor einem Jahr, als der Großteil des aktuellen Android-Teams bei upday zu arbeiten begann, war die Anwendung weit davon entfernt, die robuste, stabile App zu sein, die wir uns wünschten. Wir versuchten zu verstehen, warum unser Code in so schlechtem Zustand war, und fanden zwei Hauptursachen: ständige Änderungen an der Benutzeroberfläche und das Fehlen einer Architektur, die die von uns benötigte Flexibilität unterstützte. Die App wurde bereits zum vierten Mal innerhalb von sechs Monaten neu gestaltet. Das gewählte Entwurfsmuster schien Model-View-Controller zu sein, war aber schon damals ein „Mutant“, weit davon entfernt, wie es sein sollte.

Lassen Sie uns gemeinsam herausfinden, was das Model-View-Controller-Muster ist; wie es in Android im Laufe der Jahre angewendet wurde; wie es angewendet werden sollte, um die Testbarkeit zu maximieren; und einige seiner Vor- und Nachteile.

In einer Welt, in der sich die Logik der Benutzeroberfläche tendenziell häufiger ändert als die Geschäftslogik, brauchten Desktop- und Web-Entwickler eine Möglichkeit, die Funktionalität der Benutzeroberfläche zu trennen. Das MVC-Muster war ihre Lösung.

  • Modell – die Datenschicht, die für die Verwaltung der Geschäftslogik und die Handhabung von Netzwerk- oder Datenbank-API zuständig ist.
  • Ansicht – die Benutzeroberflächenschicht – eine Visualisierung der Daten aus dem Modell.
  • Controller – die Logikschicht, die über das Verhalten des Benutzers informiert wird und das Modell bei Bedarf aktualisiert.

Model-View-Controller Klassenstruktur

Das bedeutet also, dass sowohl der Controller als auch die View vom Model abhängen: der Controller, um die Daten zu aktualisieren, die View, um die Daten zu erhalten. Das Wichtigste für die damaligen Desktop- und Webentwickler war jedoch, dass das Modell getrennt war und unabhängig von der Benutzeroberfläche getestet werden konnte. Es erschienen mehrere Varianten von MVC. Die bekanntesten hängen damit zusammen, ob das Model passiv ist oder aktiv meldet, dass es sich geändert hat. Hier sind weitere Details:

Passives Modell

In der Version mit passivem Modell ist der Controller die einzige Klasse, die das Modell manipuliert. Basierend auf den Aktionen des Benutzers muss der Controller das Modell ändern. Nachdem das Modell aktualisiert wurde, teilt der Controller der Ansicht mit, dass sie ebenfalls aktualisiert werden muss. Zu diesem Zeitpunkt fordert die View die Daten vom Model an.

Modell-View-Controller – passives Modell – Verhalten

Aktives Modell

Für die Fälle, in denen der Controller nicht die einzige Klasse ist, die das Modell modifiziert, benötigt das Modell eine Möglichkeit, die View und andere Klassen über Aktualisierungen zu informieren. Dies wird mit Hilfe des Observer-Musters erreicht. Das Modell enthält eine Sammlung von Beobachtern, die an Aktualisierungen interessiert sind. Die View implementiert die Beobachterschnittstelle und registriert sich als Beobachter beim Model.

Model-View-Controller – aktives Model – Klassenstruktur

Jedes Mal, wenn das Model aktualisiert wird, durchläuft es auch die Sammlung der Beobachter und ruft die Methode update auf. Die Implementierung dieser Methode in der View wird dann die Abfrage der neuesten Daten aus dem Model auslösen.

Model-View-Controller – aktives Model – Verhalten

Model-View-Controller in Android

Um 2011 herum, als Android immer populärer wurde, tauchten natürlich Fragen zur Architektur auf. Da MVC zu dieser Zeit eines der beliebtesten UI-Patterns war, versuchten Entwickler, es auch auf Android anzuwenden.

Wenn man auf StackOverflow nach Fragen wie „How to apply MVC in Android“ sucht, lautet eine der beliebtesten Antworten, dass in Android eine Activity sowohl die View als auch der Controller ist. Im Nachhinein betrachtet, klingt das fast verrückt! Aber damals lag der Schwerpunkt darauf, das Modell testbar zu machen, und die Wahl der Implementierung von View und Controller war in der Regel von der Plattform abhängig.

Wie sollte MVC in Android angewendet werden

Heutzutage ist die Frage, wie die MVC-Muster anzuwenden sind, einfacher zu beantworten. Die Activities, Fragments und Views sollten die Views in der MVC-Welt sein. Die Controller sollten separate Klassen sein, die keine Android-Klassen erweitern oder verwenden, und das Gleiche gilt für die Models.

Ein Problem ergibt sich bei der Verbindung des Controllers mit der View, da der Controller der View mitteilen muss, dass sie aktualisiert werden soll. In der passiven Model-MVC-Architektur muss der Controller einen Verweis auf die View halten. Der einfachste Weg, dies zu tun und sich dabei auf das Testen zu konzentrieren, ist eine BaseView-Schnittstelle, die die Activity/Fragment/View erweitern würde. Der Controller hätte also einen Verweis auf die BaseView.

Vorteile

Das Model-View-Controller-Muster unterstützt in hohem Maße die Trennung von Belangen. Dieser Vorteil erhöht nicht nur die Testbarkeit des Codes, sondern macht ihn auch leichter erweiterbar und erlaubt eine relativ einfache Implementierung neuer Features.

Die Model-Klassen haben keine Referenz zu Android-Klassen und sind daher einfach zu testen. Der Controller erweitert oder implementiert keine Android-Klassen und sollte einen Verweis auf eine Interface-Klasse der View haben. Auf diese Weise ist auch ein Unit-Test des Controllers möglich.

Wenn die Views das Prinzip der einzigen Verantwortung respektieren, dann besteht ihre Rolle nur darin, den Controller für jedes Benutzerereignis zu aktualisieren und nur Daten aus dem Model anzuzeigen, ohne irgendeine Geschäftslogik zu implementieren. In diesem Fall sollten UI-Tests ausreichen, um die Funktionalitäten der View abzudecken.

Die View hängt vom Controller und vom Model ab

Die Abhängigkeit der View vom Model beginnt bei komplexen Views ein Nachteil zu sein. Um die Logik in der View zu minimieren, sollte das Model in der Lage sein, testbare Methoden für jedes anzuzeigende Element bereitzustellen. In einer aktiven Model-Implementierung erhöht dies die Anzahl der Klassen und Methoden exponentiell, da Beobachter für jeden Datentyp erforderlich wären.

Da die View sowohl vom Controller als auch vom Model abhängt, können Änderungen in der UI-Logik Aktualisierungen in mehreren Klassen erfordern, was die Flexibilität des Patterns verringert.

Wer kümmert sich um die UI-Logik?

Gemäß dem MVC-Muster aktualisiert der Controller das Model und die View erhält die anzuzeigenden Daten vom Model. Aber wer entscheidet darüber, wie die Daten angezeigt werden sollen? Ist es das Modell oder die Ansicht? Betrachten wir das folgende Beispiel: Wir haben einen User mit Vor- und Nachnamen. In der View müssen wir den Benutzernamen als „Nachname, Vorname“ anzeigen (z.B. „Doe, John“).

Wenn die Rolle des Models nur darin besteht, die „rohen“ Daten bereitzustellen, bedeutet das, dass der Code in der View folgendermaßen aussehen würde:

String firstName = userModel.getFirstName(); 
String lastName = userModel.getLastName(); nameTextView.setText(lastName + ", " + firstName)

Das bedeutet also, dass die View für die Handhabung der UI-Logik verantwortlich wäre. Das macht es aber unmöglich, die UI-Logik zu testen.

Der andere Ansatz besteht darin, dass das Modell nur die Daten preisgibt, die angezeigt werden müssen, und die Geschäftslogik vor der Ansicht verbirgt. Aber dann haben wir Modelle, die sowohl Geschäfts- als auch UI-Logik verarbeiten. Das wäre zwar unit-testbar, aber das Model ist dann implizit von der View abhängig.

String name = userModel.getDisplayName(); nameTextView.setText(name);

Fazit

In den Anfangstagen von Android schien das Model-View-Controller-Pattern viele Entwickler zu verwirren und führte zu Code, der schwierig, wenn nicht gar unmöglich zu testen war.

Die Abhängigkeit der Ansicht vom Modell und die Logik in der Ansicht führten dazu, dass unsere Codebasis in einen Zustand geriet, von dem es unmöglich war, sich zu erholen, ohne die Anwendung komplett umzugestalten. Was war der neue Ansatz in der Architektur und warum? Finden Sie es heraus, indem Sie diesen Blogbeitrag lesen.