Android architectuurpatronen deel 1: Model-View-Controller

Florina Muntenescu
Florina Muntenescu

Follow

1 nov, 2016 – 6 min read

Een jaar geleden, toen het grootste deel van het huidige Android-team bij upday begon te werken, was de applicatie bij lange na niet de robuuste, stabiele app die we wilden dat het zou zijn. We probeerden te begrijpen waarom onze code in zo’n slechte staat was en we vonden twee belangrijke boosdoeners: het continu veranderen van de UI en het ontbreken van een architectuur die de flexibiliteit ondersteunde die we nodig hadden. De app was al aan zijn vierde herontwerp toe in zes maanden. Het gekozen ontwerppatroon leek Model-View-Controller te zijn, maar was toen al een “mutant”, verre van hoe het zou moeten zijn.

Laten we samen ontdekken wat het Model-View-Controller patroon is; hoe het in de loop der jaren in Android is toegepast; hoe het zou moeten worden toegepast zodat het de testbaarheid kan maximaliseren; en enkele van zijn voor- en nadelen.

In een wereld waarin de gebruikersinterface-logica de neiging heeft vaker te veranderen dan de bedrijfslogica, hadden de desktop- en webontwikkelaars een manier nodig om de gebruikersinterface-functionaliteit te scheiden. Het MVC patroon was hun oplossing.

  • Model – de data laag, verantwoordelijk voor het beheren van de business logica en het afhandelen van netwerk of database API.
  • View – de UI laag – een visualisatie van de gegevens uit het Model.
  • Controller – de logica laag, krijgt bericht van het gedrag van de gebruiker en werkt het Model bij indien nodig.

Model-View-Controller klassenstructuur

Dit betekent dus dat zowel de Controller als de View afhankelijk zijn van het Model: de Controller om de gegevens bij te werken, de View om de gegevens op te halen. Maar, het belangrijkste voor de desktop- en webontwikkelaars in die tijd: het Model was gescheiden en kon onafhankelijk van de UI worden getest. Verscheidene varianten van MVC verschenen. De bekendste hebben te maken met de vraag of het Model passief is of actief meldt dat het veranderd is. Hier zijn meer details:

Passief Model

In de Passief Model versie is de Controller de enige klasse die het Model manipuleert. Op basis van de acties van de gebruiker moet de Controller het Model wijzigen. Nadat het Model is bijgewerkt, zal de Controller de View informeren dat het ook moet worden bijgewerkt. Op dat moment zal de View de gegevens uit het Model opvragen.

Model-View-Controller – passief Model – gedrag

Actief Model

Voor de gevallen waarin de Controller niet de enige klasse is die het Model wijzigt, heeft het Model een manier nodig om de View, en andere klassen, op de hoogte te stellen van updates. Dit wordt bereikt met behulp van het Observer pattern. Het Model bevat een verzameling van observers die geïnteresseerd zijn in updates. De View implementeert de observer-interface en registreert zich als observer bij het Model.

Model-View-Controller – actief Model – klassestructuur

elke keer dat het Model wordt bijgewerkt, zal het ook door de verzameling observers itereren en de methode update aanroepen. De implementatie van deze methode in de View zal dan het opvragen van de nieuwste gegevens van het Model in gang zetten.

Model-View-Controller – actief Model – gedrag

Model-View-Controller in Android

Omstreeks 2011, toen Android steeds populairder begon te worden, verschenen er natuurlijk vragen over de architectuur. Aangezien MVC destijds een van de populairste UI-patronen was, probeerden ontwikkelaars dit ook toe te passen op Android.

Als je op StackOverflow zoekt naar vragen als “Hoe pas ik MVC toe in Android?”, stelt een van de populairste antwoorden dat in Android een Activiteit zowel de View als de Controller is. Achteraf gezien klinkt dit bijna gek! Maar, op dat moment lag de nadruk vooral op het testbaar maken van het Model en meestal was de implementatie keuze voor de View en de Controller afhankelijk van het platform.

Hoe moet MVC worden toegepast in Android

Heden ten dage heeft de vraag hoe de MVC patronen moeten worden toegepast een antwoord dat makkelijker te vinden is. De Activities, Fragments en Views zouden de Views moeten zijn in de MVC wereld. De Controllers zouden aparte klassen moeten zijn die geen enkele Android klasse uitbreiden of gebruiken, en hetzelfde voor de Models.

Een probleem ontstaat bij het verbinden van de Controller met de View, omdat de Controller de View moet vertellen dat hij moet updaten. In de passieve Model MVC architectuur, de Controller moet een verwijzing naar de View te houden. De gemakkelijkste manier om dit te doen, terwijl men zich concentreert op het testen, is om een BaseView interface te hebben, die de Activiteit/Fragment/View zou uitbreiden. Dus, de Controller zou een verwijzing naar de BaseView hebben.

Voordelen

Het Model-View-Controller patroon ondersteunt in hoge mate de scheiding van belangen. Dit voordeel verhoogt niet alleen de testbaarheid van de code, maar het maakt het ook gemakkelijker om uit te breiden, waardoor een vrij eenvoudige implementatie van nieuwe functies.

De Model klassen hebben geen verwijzing naar Android klassen en zijn daarom eenvoudig te unit-testen. De Controller breidt geen Android klassen uit of implementeert ze niet en moet een verwijzing hebben naar een interface klasse van de View. Op deze manier is unit testing van de Controller ook mogelijk.

Als de Views het single responsibility principe respecteren dan is hun rol alleen om de Controller bij te werken voor elke user event en alleen gegevens van het Model te tonen, zonder enige business logica te implementeren. In dit geval zouden UI tests voldoende moeten zijn om de functionaliteiten van de View te dekken.

The View Depends On The Controller And On The Model

De afhankelijkheid van de View van het Model begint een nadeel te worden in complexe Views. Om de logica in de View te minimaliseren, zou het Model in staat moeten zijn om testbare methods te leveren voor elk element dat getoond gaat worden. In een actieve Model implementatie, verhoogt dit exponentieel het aantal klassen en methodes, gezien het feit dat Observers voor elk type van gegevens nodig zouden zijn.

Gezien het feit dat de View afhankelijk is van zowel de Controller als het Model, kunnen veranderingen in de UI logica updates in verschillende klassen vereisen, wat de flexibiliteit van het patroon vermindert.

Wie handelt de UI-logica af?

Volgens het MVC-patroon werkt de Controller het Model bij en krijgt de View de weer te geven gegevens van het Model. Maar wie beslist over de weergave van de gegevens? Is dat het Model of de View? Beschouw het volgende voorbeeld: we hebben een User, met voornaam en achternaam. In de View moeten we de gebruikersnaam weergeven als “Achternaam, Voornaam” (b.v. “Doe, John”).

Als de rol van het Model is om alleen de “ruwe” gegevens te verschaffen, betekent dit dat de code in de View zou zijn:

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

Dus dit betekent dat het de verantwoordelijkheid van de View zou zijn om de UI logica af te handelen. Maar dit maakt het onmogelijk om de UI-logica te unit-testen.

De andere benadering is om het Model alleen de gegevens te laten tonen die getoond moeten worden, en de bedrijfslogica te verbergen voor de View. Maar dan krijgen we modellen die zowel bedrijfs- als UI-logica verwerken. Het zou unit testbaar zijn, maar dan eindigt het Model impliciet afhankelijk te zijn van de View.

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

Conclusie

In de begindagen van Android leek het Model-View-Controller patroon veel ontwikkelaars in verwarring te hebben gebracht en te hebben geleid tot code die moeilijk, zo niet onmogelijk te unit-testen was.

De afhankelijkheid van de View van het Model en het hebben van logica in de View stuurde onze code-base naar een toestand waarvan het onmogelijk was te herstellen zonder de app volledig te refactoren. Wat was de nieuwe aanpak in architectuur en waarom? Kom erachter door deze blog post te lezen.