Model-Controller-View
Konstantinusz Kft. © 2011
1. Tartalomjegyzék
1.
Tartalomjegyzék ................................................................................................. 2 2. Mi az MVC? ....................................................................................................... 3 3. A probléma ........................................................................................................ 4 4. A Controller........................................................................................................ 5 5. A Modell ............................................................................................................ 6 6. A View ............................................................................................................... 7 7. A Környezet ....................................................................................................... 9 8. Modellből kezdeményezett kommunikáció ....................................................... 11 9. Validálás .......................................................................................................... 13
--2-14
2. Mi az MVC? A Model-Controller-View, röviden MCV vagy MVC, egy szoftver architektúra koncepció, ami szerint egy programot három nagy egységre oszthatunk fel. Egy teljes program nyilván foglalkozik bemenettel, az adatok feldolgozásával, tárolásával, vagy bármivel ami a tényleges feladata, illetve mindezek megjelenítésével. Így tehát a három nagy egység az Controller (irányítás), View (megjelenítés), és a Model. Utóbbi jelenti azt a működési logikát ami a program valódi célja, mint például adatok tárolása, vagy adatfeldolgozás, lekérdezés.
Az MVC elv szerint ezt a három részt külön kell választanunk egy programon belül szigorú elvek szerint, és csak meghatározott szabályok szerint érintkezhetnek egymással ezek a részek. Az, hogy erre miért van szükség, többnyire mélyebb programozási elvekből következik mint például a hordozhatóság, újrafelhasználhatóság, információ elrejtés elve, stb. A továbbiakban azt nézzük meg tehát, hogy mik meg jellemzik az MVC architektúra három egységét, hogyan viszonyulnak egymáshoz, milyen gyakorlati szituációk miatt szükséges, és hogyan kell ennek megfelelő kódot készíteni.
--3-14
3. A probléma Vegyünk példának egy szokásos felhasználói regisztrációt. Látunk egy űrlapot ahol megadjuk az adatainkat, és regisztráláskor ezekből képződik egy felhasználói fiók, ami többet között az űrlapon megadott adatainkat is tárolni fogja. Képzeljük el, hogy ennek hogy nézne ki a programkódja ami akkor fut le amikor megnyomom a “regisztrálok” gombot. Nyilván lesz egy kód ami összegyűjti az adatokat, esetleg validálja hogy mindent helyesen töltöttünk ki, majd létrehozza a felhasználói fiókot, legyen az egy objektum, adatbázis bejegyzés vagy fájl. Akik nem követik az MVC-t, azok ezt az egészet többé-kevésbé egybe írják, akár monolítikusan is. Ezzel az a gond, hogy űrlap és annak a működése össze van drótozva azzal a kóddal ami a felhasználói fiókot létrehozza. Ugyanis mi van akkor, ha mondjuk van egy adminisztrációs felület is, ahol szintén lehet felhasználói fiókokat létrehozni, viszont ez a felület nyilván más mint a regisztrációs űrlap. Ebben az esetben, ha nem követjük az MVC-t, duplán kell megírni azt a kódot ami a felhasználói fiókot létrehozza. Ezzel viszont már rögtön megszegjük a hordozhatóság és az újrafelhasználhatóság elvét. Továbbá ez a helyzet már elvben is azt mutatja, hogy a felhasználói felület és a felhasználói fiókkal kapcsolatos tevékenység nem tartozik egyértelműen össze, mivel felület több is lehet, amik ugyanazzal a dologgal foglalkoznak. Így ezek szétválasztásával születik meg az MVC koncepciója.
A három nagy egység szétválasztása pusztán annyit jelent, hogy a kódot külön választjuk (függvényekbe, osztányokba, stb) aszerint hogy milyen szerepet töltenek be. Ez viszont még nem elég ahhoz, hogy MVC architektúránk legyen, ugyanis azt is biztosítanunk kell, hogy az egységek megfelelő szabályok szerint kommunikáljanak egymással. Most tehát nézzük sorban, hogy mik azok a tulajdonságok amik eldöntik egy adott kódról, hogy hova tartozik.
--4-14
4. A Controller Ide tartozik minden ami valamilyen bemenet fogadásával, feldolgozásával foglalkozik. A regisztrációs felületünk esetében ez egyrészt a szövegbevitelt és a felület gombjaira kattintást jelenti. Ezzel viszont még nincs vége a controller munkájának. Például a “regisztráció” gomb megnyomásánál egyrészt el kell tudni dönteni, hogy pontosan milyen műveletet is kell végrehajtani, és ahhoz milyen adatok kellenek a bementről. Ezeket az adatokat ugyanis még össze kell szedni, és végül átadni a modellnek, ami végül ténylegesen elvégzi a regisztrációt. Ideális esetben a controller csak egyetlen függvény-, vagy metódushívást intéz a modellhez minden egyes műveletnél, mint például a “regisztráció” gomb megnyomása. Ez az elv általánosan igaz az MVC többi egysége közti kommunikációra is: a felület keresztmetszetét minimalizálni kell (minnél kevesebb függvény/metódushívás kelljen hozzá). Ez szintén a hordozhatóságot javítja.
--5-14
5. A Modell Az egész MVC-nek a talán leglényegesebb része a modell, hiszen itt történik a program valódi működése. Ez az a része egy szoftvernek ami elsőre legtöbbeknek nem egyértelmű hogy micsoda, meg hogy egyáltalán létezik.
Képletesen szólva a modell az a rész ami akkor marad, ha kihúzom a monitort, a hangszórót, az egeret, a billentyűzetet, és ami ezek nélkül is működik, na az a modell. A modell az alkalmazás agya. A modell adja az alkalmazás működési logikáját, a benne tárolt adatokkal együtt. Tehát a modell adat-, és eljárásmodellből is áll, vagyis teljes értékű objektum modell.
A modellnek a legfőbb követelménye, hogy nem olvashat közvetlenül semmilyen bemenetről, és nem állíthat elő közvetlenül semmilyen kimenetet.
Ez azt is jelenti, hogy ha például egy hibaüzenetet kell hogy visszaadjon a modell egy objektuma, akkor nem lehet benne a modell kódjában literálisan az üzenet olvasható szövege. Egy hibakódot viszont visszadhat, ami alapján a megjelenítésnél képződik egy olvasható üzenet.
Továbbá a modell soha nem kezdeményezhet közvetlen kommunikációt a view-val és a controller-el, tehát nem hívhatja meg azok metódusait, nem példányosíthatja azok osztályait, stb. Elvi szinten a modell nem is tudhat a másik két egység létezéséről. Ez azt jelenti, hogy mindig a controller és a view kapja meg először a vezérlést, és azok hívják meg a modell metódusait.
--6-14
6. A View A view nyilván a megjelenítéssel foglalkozik, így kizárásos alapon minden ami nem controllerbe és a modellbe tartozik, az view. Természetesen a view leginkább modellről fog megjelenítést végezni, így a view is hozzányúlhat a modellhez. Ami fontos, hogy a vezérlés mindig a view-nak adódik át utoljára. Ez nyilván azért fontos mert ha valamilyen bemenet volt ami kiváltott valami változást, akkor annak a végeredményét akarjuk látni. Persze ez az elv nem igazán áll fenn olyan alkalmazásoknál amik több szálon futnak, hiszen ott a párhuzamosság miatt nem biztos, hogy értelmezhető futási sorrend a view és a program többi része között. Mindenesetre elmondhatjuk, hogy a megjelenítésnél mindig a megváltzotott állapotot akarjuk látni.
Kérdéses még, hogy a controller és a view hogyan viszonyulnak egymáshoz? Nos, azt tudjuk hogy a controller kap előbb vezérlést, és az is igaz, hogy a controller irányíthatja a view-t is. Hiszen gondoljunk bele, egy gombnyomásra megnyílhat egy új ablak, aminek semmi köze a modellhez, egyszerűen csak a view állapota lesz manipulálva. Viszont az nem egyértelmű, hogy a view pontosan hogyan kap vezérlést, miután a controller befejezte a munkáját. Erre két lehetőség van. Az egyik, hogy az a kód ami controllert lefutatta, lefuttatja a view-t is utána, függetlenül bármilyen körülménytől, vagy attól hogy mi történt a controller-ben. A másik út, hogy a controller futtatja le a view-t amikor végzett a saját dolgával. Személy szerint én az utóbbit nem tartom általánosan helyes megoldásnak, mivel nem minden esetben követ egy controllerben bekövetkezett eseményt megjelenítés. Aktív alkalmazásoknál pedig controller esemény nélkül is futnia kell a view-nak. Továbbá több szálas alkalmazásoknál pedig nem is beszélhetünk igazán futási sorrendről, tehát ott ez az elv nem is értelmezhető.
Abban is lehetnek eltérő megoldások, hogy hogyan határozódik meg, hogy mit kell megjelenítteni a view-ban. Az egyik lehetőség, hogy a view lefut, és saját maga összeszed a modellből minden információt amire szüksége van. A másik lehetőség, hogy a controller szedi össze ezeket az adatokat, és “feltölti” vele a view-t mielőtt az megjelenne, így a view csak megkapja az adatokat, és nem is kell tudnia hogy azok hogyan és miként álltak elő. Ez egyfelől jó, mert növeli az absztrakciót, viszont ez is igazából csak passzív alkalmazásoknál helytálló mint például webes alkalmazások, ahol a program minden lekéréssel elejétől a végéig lefut, amolyan kérdez-felelek
--7-14
módon. Aktív alkalmazásoknál (például egy játékprogram) viszont a view anélkül is fut, hogy bármilyen controller esemény lenne, tehát a view muszáj hogy önálló legyen, így ez a megvalósítás az általánosabb.
--8-14
7. A Környezet Egy program nem csak a saját kódjából áll, mivel kommunikálnia kell a környezettel is. Ebbe nyilván beletartozik akármilyen sztenderd kimenetet és bemenet, viszont minden más külvilággal folytatott kommunikáció is, mint például fájlokba írás/olvasás, hálózati kommunikáció, vagy akár a rendszeridő lekérdezése.
Már tisztáztuk azt, hogy a modell nem produkálhat kimenetet, és bemenetről sem olvashat, viszont mi a helyzet a többivel? Nos, a válasz az, hogy igen, minden mással érintkezhet a modell közvetlenül, még hálózati kommunikációt is kezdeményezhet kifelé, mint például email küldés. Ez a tény viszont egy picit zavaró lehet, mert akkor mit jelent valójában az, hogy a modell nem produkálhat kimenetet? Igazából a különbség csak az, hogy a kommunikáció a felhasználó felé irányul vagy nem. A modell nem beszél a felhasználó nyelvén, ebben segít tolmácsolni a controller és a view, viszont egy másik számítógép nyelvén igen. Márpedig a hálózati kommunikáció többnyire erről szól. Sőt, ha belegondolunk nincs is sok különbség aközött, hogy a modell egy másik, a modellen belüli objektumnak küld “üzenetet”, vagy egy távoli gépen lévő másik modellnek.
Ugyan az MVC csak az alkalmazás belső struktúrájára vonatkozik, viszont a környezettel való kapcsolatra is alkalmazhatjuk ugyanazokat az elveket, amiket az MVC-hez használtunk. A architektúra akkor a legszebb, ha az alkalmazás környezet függetlenné is válik. Ez azt jelenti, hogy semmilyen része nem kommunikál közvetlenül a környezettel, ami alatt értünk minden kimenetet, bemenetet, fájlrendszer, hálózat, rendszeridő, operációs rendszerrel való kapcsolat, egyszóval minden kód amit nem mi írtunk, hanem a nyelvbe / fejlesztői környezetbe van beépítve. Helyette ezeket el kell fednünk úgynevezett absztrakciós interfészekkel, ami pedig nem más, mint egy általunk írt objektum vagy objektum modell. Ezek igazából semmit mást nem tesznek, csak ugyanazokat a szolgáltatásokat nyújtják (akár egy az egybe ugyanazokat a metódusokat), mint azok a dolgok amit elfednek, így ezzel lényegében csak közvetítenek az alkalmazás és a környezet között.
Jogos kérdés persze, hogy ez mire jó, és miért kellene még külön ezzel is foglalkozni, mikor látszólag ezek az interfészek pontosan ugyanazt csinálják mint az eredeti. Mit nyerünk vele? A válasz igen egyszerű: hordozhatóságot és platform függetlenséget. Ugyanis ekkor megtehetem, hogy átviszem az alkalmazásomat
--9-14
valamilyen teljesen más környezetbe, más platformra, sőt másik operációs rendszerbe, és így csak az absztrakciós interfészek implementációját kell az új környezethez alakítani, ahelyett hogy mindenhol bele kellene nyúlni az alkalmazásba, ahol a környezettel kommunikál. Ráadásul az ilyen szituációk szintén azt mutatják, hogy a modellnek nem szabad tudni a controller-ről és a view-ról, hiszen azok teljes egészében mások lehetnek ha egyik platformról a másikra kell költöztetni az alkalmazást. Sok szoftver van amiknek több operációs rendszerre is van implementációjuk, és abszurd lenne feltételezni, hogy ehhez több, teljesen különböző verziót fejlesztenek egymástól függetlenül.
--10-14
8. Modellből kezdeményezett kommunikáció Mint tudjuk, a modell sosem kezdeményezhet közvetlen kommunikációt a controller vagy a view felé, lényegében nem is tud róluk. De vajon tényleg csak olyan szituációk léteznek ahol a controller vagy a view irányából indul egy esemény? A helyzet az, hogy nem.
Gondoljunk egy olyan helyzetre, ahol van egy aktív alkalmazásunk, tehát folyamatosan fut a modellben valami kód, ami egyszer csak valamiről szeretne értesíteni minket (például hibaüzenet vagy kérdés), és emiatt meg kellene nyitni egy ablakot a felhasználói felületen. Például egy fájlokat másoló kód talál egy létező fájl, és rá akar kérdezni, hogy felülírhatja-e? A fájlok másolása nyilván teljes egészében modell kód. Itt bizony tényleg a modellből indul az esemény ami végül a megjelenítést is kell hogy befolyásolja. Ez azt jelenti, hogy az MVC elvei nem igazak? De igen, csak egy kis kerülő megoldásra van szükségünk.
A megoldás az eseménykezelés. Ez az architektúrális bravúr azokra a szituációkra van kitalálva, ahol a függőség irányával ellentétesen kell kezdeményeznünk kommunikációt. Ugyebár a view tudhat a modellről, de fordítva nem. Az ötlet az, hogy a view fel tud iratkozni “figyelőnek” a modell egyes eseményeire, anélkül, hogy a modell előre tudná, hogy ki vagy mi iratkozik fel. Amikor pedig a modellben bekövetkezik a kérdéses esemény, akkor átadja a vezérlést az összes, az adott eseményre feliratkozott figyelőnek, esetlegesen átadva az eseményhez kötődő fontos adatokat.
Ez technikailag azt jelenti OO architektúrában, hogy a modell rendelkezésre bocsát egy interfészt amit a figyelőknek kell implementálnia. Ez az interfész tartalmaz egy metódust ami meg fog hívódni amikor az adott esemény bekövetkezik. Ezen túl, a modellnek lesz egy esemény menedzser objektuma, amin keresztül feliratkozhatnak a figyelők (azok az objektumok amik implementálják az imént említett interfészt), megnevezve azt is, hogy mely eseményre iratkoznak fel (az eseménynek van valamilyen előre meghatározott azonosítója vagy neve). Ezután, ha modell egy adott eseménynél szeretné értesíteni a figyelőket, akkor szól az esemény menedzsernek, hogy az adott eseményhez bekövetkezett, átadva a szükséges információkat is. Ekkor az esemény menedzser lefuttatja a feliratkozott figyelőknek az a bizonyos
--11-14
metódusát, és kész is vagyunk. Ezután persze a tényleges ablakot megnyitó kódot már a figyelő hívja meg, így tehát a modell számára továbbra is ismeretlen marad.
Tehát nincs semmi baj, az MVC szabályai továbbra is állnak. Valójában ebben az esetben is igaz az, hogy a view kezdeményezte kommunikációt, azzal hogy a feliratkozást megtette.
--12-14
9. Validálás Általában amikor valamilyen bemenetet kap egy alkalmazás, az nem mindig olyan információ ami megengedett egy adott helyzetben. Például egy ha űrlapot töltünk ki, lehetnek kötelező mezők, lehetnek olyanok ahova csak számokat szabad írni, vagy ahol a bevitt szöveg hossza korlátozva van, stb. Nagyon fontos az, hogy ezeket a validálásokat végző kódokat hova soroljuk az MVC-ben? Mint tudjuk, az alkalmazás valódi működési logikáját a modell adja. Ide jutnak el a lényeges adatok, itt kerülnek tárolásra, feldolgozásra. Biztosan állíthatjuk tehát, hogy modellnek mindenképpen helyes adatokra van szüksége. A kérdés, hogy ezek hol leszek ellenőrizve? Sokan azt mondanák, hogy a controllerben, hiszen az foglalkozik a bemenettel. Csakhogy ezzel van egy kis gond.
Mint ahogy fentebb láttuk, a controller és a view csak egy héj a modell körül, ami összeköti azt egy adott külvilággal, és egy modellhez nem tartozik egyértelműen egy view vagy controller, így azokból több is lehet egyidejűleg. Gondoljunk a fentebb leírt felhasználói regisztráció példájára, ahol van egy felület a felhasználók számára és van egy az adminisztrátor számára. Ezek mögött különböző controller kód lehet. Így tehát a validáló kódot is kétszer kellene megírni. Ezért inkább ésszerűnek tűnik, hogy a modell validáljon, hiszen abból csak egy van. De nem csak ez az egyetlen oka. Egyrészt, ha függőségi szempontból nézzük, akkor maga a modell igényli és feltételezi az adatok helyességét. Ez az jelenti, hogy nem bízhatja rá magát vakon a külső környezetre, hogy abból helyes adatokat kap majd (hiszen nem is tud a controller létezéséről). Nem függhet olyantól amit nem is ismer. Ezért modellnek önállónak kell lennie validálási szempontból is. Minden, kívülről jövő információt saját magának kell ellenőriznie. Ez persze nem jelenti azt, hogy egy controller nem végezhet elő-validálást (például nem enged több karaktert írni egy mezőbe). De ez akkor sem helyettesítheti soha a modellben történő validálást. Ha pedig ez azt jelenti, hogy az adat kétszer lesz validálva, ám legyen.
Nem, ez nem felesleges túlbiztosítás. Rettenetesen fontos, hogy a modellbe rossz adatok ne kerülhessenek. Egy szoftverben az adatok felbecsülhetetlen értékkel bírnak! Ha egy adatbázis elkezd korrupt adatokkal feltöltődni, az sokkal nagyobb károkat tud okozni mint egy nem működő kód. A korrupt adatok további korrupt adatokat eredményezhetnek, és sokszor csak akkor derülnek ki amikor már olyan sok van, hogy nem lehet kezelni. Ezért a modellnek atombiztosnak kell lennie.
--13-14
Ráadásul ez biztonságtechnikai kérdés is, mivel ha csak a controller validál, akkor elképzelhető, hogy azt egy támadó valahogy megkerüli, és bejuttat a modellbe nem kívánt adatokat. Semmit nem szabad a külvilágra bízni! A modellt várfal, vizesárok és szögesdrót kell hogy védje!
--14-14