Android-arkkitehtuurimallit osa 1: Model-View-Controller

Florina Muntenescu
Florina Muntenescu

Follow

Nov 1, Yritimme ymmärtää, miksi koodimme oli niin huonossa kunnossa, ja löysimme kaksi pääsyyllistä: käyttöliittymän jatkuva muuttaminen ja sellaisen arkkitehtuurin puuttuminen, joka tukisi tarvitsemaamme joustavuutta. Sovellus oli jo neljännessä uudelleensuunnittelussa kuuden kuukauden aikana. Valittu suunnittelumalli näytti olevan Model-View-Controller, mutta se oli silloin jo ”mutantti”, kaukana siitä, miten sen pitäisi olla.

Tutustutaan yhdessä siihen, mikä Model-View-Controller-malli on, miten sitä on sovellettu Androidissa vuosien varrella, miten sitä pitäisi soveltaa, jotta se maksimoi testattavuuden, sekä joihinkin sen etuihin ja haittoihin.

Maailmassa, jossa käyttöliittymälogiikalla on taipumus muuttua useammin kuin liiketoimintalogiikalla, työpöytä- ja web-kehittäjät tarvitsivat tavan erottaa käyttöliittymätoiminnallisuus toisistaan. MVC-malli oli heidän ratkaisunsa.

  • Model – tietokerros, vastaa liiketoimintalogiikan hallinnasta ja verkon tai tietokannan API:n käsittelystä.
  • View – käyttöliittymäkerros – mallin tietojen visualisointi.
  • Controller – logiikkakerros, saa tiedon käyttäjän käyttäytymisestä ja päivittää mallia tarpeen mukaan.

Model-View-Controller-luokkarakenne

Tämä tarkoittaa siis sitä, että sekä Controller että View ovat riippuvaisia Modelista: Controller päivittää dataa, View hakee dataa. Mutta mikä tuon ajan työpöytä- ja web-kehittäjille oli tärkeintä: Model oli erotettu ja sitä voitiin testata käyttöliittymästä riippumatta. MVC:stä ilmestyi useita muunnelmia. Tunnetuimmat niistä liittyvät siihen, onko Model passiivinen vai ilmoittaako se aktiivisesti, että se on muuttunut. Tässä tarkemmin:

Passiivinen malli

Passiivisen mallin versiossa Controller on ainoa luokka, joka manipuloi mallia. Käyttäjän toimien perusteella Controller joutuu muokkaamaan Modelia. Kun Model on päivitetty, Controller ilmoittaa View:lle, että myös sen on päivitettävä. Tässä vaiheessa näkymä pyytää tietoja Modelista.

Model-View-Controller – passiivinen Malli – käyttäytyminen

Aktiivinen Malli

Tapauksia varten, joissa Valvoja ei olekaan ainoa luokka, joka muokkaa Mallia, Malli tarvitsee tavan, jolla se voi ilmoittaa Näkymälle (View) ja toisille luokille (Other Class) päivityksistä. Tämä onnistuu Observer-mallin avulla. Malli sisältää kokoelman tarkkailijoita, jotka ovat kiinnostuneita päivityksistä. View toteuttaa observer-rajapinnan ja rekisteröityy Modelin observeriksi.

Model-View-Controller – Aktiivinen Model – luokkarakenne

Joka kerta, kun Model päivittää tietojaan, se myös käy läpi observer-kokoelman ja kutsuu metodia update. Tämän metodin toteutus Näkymässä käynnistää tällöin uusimpien tietojen pyytämisen Mallista.

Model-View-Controller – aktiivinen Malli – käyttäytyminen

Model-View-Controller Androidissa

Noin vuoden 2011 tienoilla, kun Androidista alkoi tulla koko ajan suositumpi ja tunnetumpi, arkkitehtuuriin liittyi luonnollisesti myös kysymyksiä. Koska MVC oli tuolloin yksi suosituimmista UI-malleista, kehittäjät yrittivät soveltaa sitä myös Androidiin.

Jos etsit StackOverflow’sta kysymyksiä kuten ”Kuinka soveltaa MVC:tä Androidissa”, yhdessä suosituimmista vastauksista todettiin, että Androidissa Activity on sekä View että Controller. Jälkikäteen ajateltuna tämä kuulostaa lähes hullulta! Mutta tuolloin pääpaino oli mallin testaamiskelpoiseksi tekemisessä, ja yleensä toteutuksen valinta Näkymälle ja Ohjaimelle riippui alustasta.

Miten MVC:tä pitäisi soveltaa Androidissa

Tänä päivänä kysymykseen siitä, miten MVC-malleja tulisi soveltaa, on helpompi löytää vastaus. Aktiviteettien, fragmenttien ja näkymien pitäisi olla MVC-maailmassa näkymiä. Controllereiden tulisi olla erillisiä luokkia, jotka eivät laajenna tai käytä mitään Android-luokkaa, ja sama pätee myös Modelleihin.

Yksi ongelma syntyy, kun Controller yhdistetään View:hen, sillä Controllerin täytyy käskeä View:tä päivittämään. Passiivisessa Model MVC -arkkitehtuurissa Controllerin täytyy pitää hallussaan viittausta View:hen. Helpoin tapa tehdä tämä, samalla kun keskitytään testaukseen, on olla BaseView-rajapinta, jota Activity/Fragment/View laajentaisi. Ohjaajalla olisi siis viittaus BaseViewiin.

Hyötyjä

Malli-näkymä-ohjausmalli tukee suuresti huolenaiheiden erottelua. Tämä etu ei ainoastaan lisää koodin testattavuutta, vaan se myös helpottaa sen laajentamista ja mahdollistaa uusien ominaisuuksien melko helpon toteuttamisen.

Model-luokilla ei ole viittauksia Android-luokkiin, joten niiden yksikkötestaus on suoraviivaista. Controller ei laajenna tai toteuta mitään Android-luokkia, ja sillä pitäisi olla viittaus johonkin View-rajapintaluokkaan. Näin myös Controllerin yksikkötestaaminen on mahdollista.

Jos Näkymät noudattavat yhden vastuun periaatetta, niiden tehtävänä on vain päivittää Controlleria jokaisen käyttäjätapahtuman yhteydessä ja vain näyttää tietoja Modelista, toteuttamatta mitään liiketoimintalogiikkaa. Tällöin UI-testien pitäisi riittää kattamaan näkymän toiminnallisuudet.

Näkymä riippuu ohjaimesta ja mallista

Näkymän riippuvuus mallista alkaa olla haittapuolena monimutkaisissa näkymissä. Näkymän logiikan minimoimiseksi mallin pitäisi pystyä tarjoamaan testattavia metodeja jokaiselle elementille, joka tulee näytettäväksi. Aktiivisessa Model-toteutuksessa tämä lisää eksponentiaalisesti luokkien ja metodien määrää, kun otetaan huomioon, että jokaiselle tietotyypille tarvittaisiin tarkkailijoita.

Koska Näkymä on riippuvainen sekä Controllerista että Modelista, muutokset käyttöliittymälogiikassa saattavat vaatia päivityksiä useisiin luokkiin, mikä vähentää mallin joustavuutta.

Kuka käsittelee käyttöliittymälogiikkaa?

MVC-kuvion mukaan Controller päivittää Modelin ja View saa näytettävät tiedot Modelista. Mutta kuka päättää, miten tiedot näytetään? Onko se Model vai View? Tarkastellaan seuraavaa esimerkkiä: meillä on User, jossa on etunimi ja sukunimi. Näkymässä meidän on näytettävä käyttäjän nimi muodossa ”Sukunimi, Etunimi” (esim. ”Doe, John”).

Jos Mallin tehtävänä on vain tarjota ”raakadata”, se tarkoittaa, että Näkymässä oleva koodi olisi:

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

Tämä tarkoittaa siis sitä, että Näkymän vastuulla olisi UI-logiikan käsittely. Mutta tämä tekee UI-logiikasta mahdottoman yksikkötestauksen.

Toinen lähestymistapa on, että Model paljastaa vain näytettävän datan ja piilottaa kaiken liiketoimintalogiikan View’lta. Mutta tällöin päädytään Malleihin, jotka käsittelevät sekä liiketoiminta- että käyttöliittymälogiikkaa. Se olisi yksikkötestattavissa, mutta silloin Malli päätyy epäsuorasti riippuvaiseksi Näkymästä.

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

Johtopäätös

Androidin alkuaikoina Model-View-Controller-malli näytti hämmentäneen monia kehittäjiä ja johtaneen koodiin, jonka yksikkötestaus oli vaikeaa, ellei jopa mahdotonta.

Näkymän riippuvuus mallista ja logiikan oleminen näkymässä ohjasivat koodipohjaamme tilaan, josta oli mahdotonta toipua ilman sovelluksen täydellistä refaktorointia. Mikä oli uusi lähestymistapa arkkitehtuurissa ja miksi? Se selviää lukemalla tämä blogikirjoitus.