Itseäni on alkanut kiinnostaa, että mikä onkaan Modelin tarkka olemus MVC-arkkitehtuurissa. Jotenkin tuntuu, että MVC:tä tulkitaan hieman erilailla eri ohjelmistokehyksissä ja oikeastaan monet kuvauksetkin antavat hieman erilaisen käsityksen asiasta. Kuitenkin nimenomaan Model-taso tuntuu olevan kaikkein epäselvin. Jossain toteutuksissa sen ajatellaan olevan vain Table Gateway -suunnittelumallin toteuttama taso, joka siis jollain tavalla edustaa runtime-versiota tietokannan taulusta ja taas jossain yhteyksissä sen nähdään olevan kaikesta täysin irraallaan oleva olio, joka ei peri ainuttakaan luokkaa tai toteuta ainuttakaan interfacea (esim. Javan POJO) ja tavalla tai toisella esittää juurikin jotain reaalimaailman entiteettiä kuten käyttäjää tai tuotetta. Kumpi näistä sitten onkaan oikea toteutusmalli vai voiko tähän kysymykseen edes vastata? Entä mitä kuuluu Model-olioiden tehtäviin? Se on selvää, että niiden täytyy tarjota palvelut niiden säilömän datan muokkaamiseen, mutta entäs muuten? Jos esim. käyttäjä pystyy kirjautumaan johonkin järjestelmään, niin onko silloin login-metodi loogista sijoittaa käyttäjä-luokkaan? Myöskin sekin tuntuu jotenkin oudolta, että joissakin malleissa esitetään, että näkymäkin pystyy olemaan suorassa yhteydessä malliin eli ohjainta ei tarvita väliin. Tällöin voin mielestäni kyseenalaistaa, että mihin sitä sitten enää tarvitsee koko kontrolleria?
Itse en ole koskaan löytänyt totuutta tuosta asiasta. Olen kuitenkin jonkin tämän kaltaisen selityksen kuullut kun ihmettelin asiaa biljardia pelatessa joskus kauan sitten.
Malli on pallo. Se voi olla jossain kohdassa pöydällä. Se voi myös liikkua nopeudella ja siinä voi olla kierrettä. Keppikin on malli.
Kontrolleri on peli, joka ottaa kepin, kiven ja muut pallot ja vaikuttaa niihin. pelille tulee syöte keppi mallilta jota on voinut ohjata ihminen vaikka hiirellä. Lasketaan osumakohta kivelle ja minne kiven pitää mennä. kivi.liiku() ja malli toimii. Sitten näkymä on se kun on laskettu kuvaus pöydästä ja esitetään se katselijalle.
Tiukasti mietittäessä, näkymä pöydästä joutuisi pyytämään tiedot peliltä, joka on kotrolleri, mutta yleisesti on hyväksytty näkymän tiedon pyytäminen suoraan mallilta. Kontrolleri kuitenkin kertoo näkymälle, että nyt tulee piirtää pöytä.
Kaikki ovat luokkia ja kaikki ovat hieman naimisissa keskenään, mutta ainoastaan kivi voi periä pallon.
MVC-arkkitehtuurista on monta tulkintaa ja toteutusta, eikä niistä varmasti mikään ole ainoa oikea. Monille tiukoille näkemyksille ei ole järkeviä perusteluja, ei ainakaan sellaisia, jotka osoittaisivat kyseisen tavan aina muita paremmaksi. Siksi MVC:stä ei kannata vääntää yksityiskohtaista, ehdotonta sääntöjoukkoa, vaan riittää, että se on taustalla ideana, joka selventää koodin jäsentelyä ja helpottaa bugien etsimistä, refaktorointia ja koko projektin organisointia.
Mallin tehtävä on pitää tiedot järjestyksessä ja johdonmukaisina. Mallin kuuluu sisältää tietojen käsittelyyn tarvittava logiikka ja tarjota keinot kokonaisuuden käsittelyyn niin, että käyttäjä (kontrolleri) ei voi aiheuttaa tietokantaan virhetilannetta. Jos ohjelmassa on mielekästä muokata yksittäistä riviä tietokannasta, malli voi sallia sen suoraan. Jos rivin mielivaltainen muuttaminen voi aiheuttaa ongelmia, mallin ei pidä sallia sitä vaan sen täytyy toteuttaa metodit, joilla saa tehtyä vain järkeviä muutoksia. Pakollisten osien lisäksi malli voi sisältää esimerkiksi apufunktioita, jotka tekevät jonkin hyödyllisen toimintosarjan, vaikkapa keräävät kaiken tiettyyn näkymään liittyvän datan valmiiksi. Näiden toimintojen osalta raja mallin ja kontrollerin välillä on hämärä ja usein myös irrelevantti.
Järjestelmissä on usein monta MVC-rakenteista osaa sisäkkäin. Mallin sisällä voisi ajatella, että mallin malli on tietokanta, mallin kontrolleri on siihen toteutettu logiikka ja mallin näkymä muodostuu niistä muuttujista ja metodeista, joiden kautta muut taas lukevat mallin tietoja. Vastaavasti näkymä voi olla kokonainen järjestelmä, jossa mallina on käyttöliittymäasetukset määräävä tietokanta, kontrolleri hallitsee eri osien näyttämistä ja piilottamista ja varsinainen näkymä on joukko jollain kuvauskielellä luotuja lomakepohjia.
Hyvin usein sanotaan, että pitäisi olla standardi rajapinta, jota kaikki toteutukset noudattavat. Tämä viittaisi siihen, että hyvin pitkälti mallin tehtävä on vain toteuttaa kyseinen rajapinta. Toisaalta on päivänselvää, että joitakin karakteristisia ominaisuuksia on pakko sallia.
Joillekin "malli" on vain tietokantaa lukeva lista, jonka sisältämät alkiot ovat esimerkiksi tavallisia PHP:n taulukoita tai StdClass-olioita, eli eivät sisällä muuta kuin luettavia ja kirjoitettavia ominaisuuksia, eivät lainkaan metodeja tai muuta sellaista. Esimerkiksi "työntekijä" olisi vain array ja "YrityksenTyöntekijät" olisi lista, joka sisältää liudan assosiatiivisia taulukoita, joista jokainen sisältää käyttäjän tiedot.
Malli ei joillekin edes edusta mitään oliota vaan se on vain wrapperi tietokantaa käyttäville metodeille, joita sen vastakappaleena toimiva view-luokka tarvitsee lukeakseen tietokantaa. Minusta tämä toimintatapa johtaa herkästi koodin duplikointiin tai spagettikoodiin ja tuottaa ongelmia siinä vaiheessa, kun tarvitsee tehdä muutoksia vaikka tietokantaan, ja yhtäkkiä korjattavia luokkia onkin N kappaletta, eikä kukaan oikeastaan tiedä, missä kaikkialla korjattavia asioita on.
Itse olen kuitenkin ajatellut asiaa aina niin, että myös jokainen käyttäjä on malli eli interaktiivinen olio, jolla on metodeja ja joka vastaa itse omien tietojensa päivittämisestä tietokantaan ja niin pois päin. Tällöin käyttäjälista olisi itse asiassa malli, jonka jokainen alkiokin (käyttäjä) on malli. Tällöin pidän huolen siitä, että aina kun jossain tarvitaan käyttäjän tietoja, niin käsitellään juuri Käyttäjä-olioita eikä tilanteesta riippuen mitä sattuu.
Ajattelen myös niin, että mallin tehtävä on käsitellä sisältämänsä data niin pitkälle kuin mahdollista, eli jos mallin tiedot näytetään vaikka taulukossa, niin malli tuottaa suurin piirtein jokaisen sarakkeen tiedot itse, eikä niitä lasketa controllerin tai viewin päässä. Eli jos näytetään työntekijöiden tuntipalkka, tehdyt tunnit ja kuukauden kokonaisansiot, niin malli laskee itse kuukauden palkan kahden ensinmainitun perusteella, ja view vain tietää sen, että se haluaa näyttää kolme saraketta.
Ja jos käyttäjä syöttää lomakkeelle ajan muodossa "hh:mm", niin controllerin homma on purkaa se sekunneiksi, joka on mallin tunnistama muoto kellonaikaa esittäville tyypeille.
Omaan ajattelutapaani on kuitenkin vaikuttanut vahvasti se, että olen koodaillut työpöytäsovelluksia Qt:lla, jossa käytetään enemmänkin model-view-paradigmaa, jossa controlleria ei ole juurikaan olemassa.
Useimmiten juuri tuntuukin, että model todellakin jakautuu kolmeen osaan: entiteettiin, mapperiin ja jonkin sortin table gatewayihin. Ottaa vaan päähän se, että useimmat valmiit ohjelmistokehykset toteuttavat modelin juurikin tällaisena table gatewayn kaltaisena tauluwrapperinä, eikä tarjoa selkeätä ORM:ää valmiiksi, jolla näitä entiteettejä saisi assosiaatioineen mapattua tauluihin ja kun tosiaan MVC:tä ei ole haluttu standardoida oikeastaan millään tavalla, niin jää sitten epäselväksi, että mitä minkäkin tason olion pitäisi tarkkaan ottaen tehdä. Loppu peleissä jää siis täysin suunnittelijan vastuulle päättää, että miten eri toiminnallisuus jakautuu tasojen kesken, vaikkakin jonkilainen osviitta on valmiina.
Aihe on jo aika vanha, joten et voi enää vastata siihen.