Designmönstret MVC - Model View Controller

Denna artikel förklarar vad designmönstet MVC (Model View Controller) löser för problem och hur man ska använda det.

I denna guiden kommer vi använda ett spel för att ge ett konkret exempel på hur man kan använda MVC. Spelet består av en kvadrat som kan röra sig på en spelplan på 5*5 rutor. Kvadraten kan bara flyttas ett steg i taget åt endera av hållen vänster, uppåt, höger och neråt, så spelet är väldigt simpelt (spelet har inget poäng, det är bara ett exempel!).

Spelplanens positioner definierar vi såhär:

Kod - Random Kod
1 2 3 4 5 +-+-+-+-+-+-→ X 1| | | | | | +-+-+-+-+-+ 2| | | | | | +-+-+-+-+-+ 3| | | | | | +-+-+-+-+-+ 4| | | | | | +-+-+-+-+-+ 5| | | | | | +-+-+-+-+-+ | ↓ Y

Om kvadraten till exempel har positionen x=3 och y=3 skulle den alltså befinna sig i rutan som är i mitten på spelplanen.

En enkel implementation av detta spel är följande kod:

<?php // Anropa alltid innan någon output skickats session_start(); ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Flytta kvadraten!</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> </head> <body> <h1>Detta är ett spel där du kan flytta en kvadrat.</h1> <?php // Om besökaren inte besökt sidan tidigare... if(!isset($_SESSION['kvadratX'])){ // ...låt kvadraten börja i mitten $_SESSION['kvadratX'] = 3; $_SESSION['kvadratY'] = 3; } // Om besökaren skickat med ett flytta-kommando, flytta kvadraten if(isset($_POST['flyttaUpp'])){ $_SESSION['kvadratY']--; }elseif(isset($_POST['flyttaNer'])){ $_SESSION['kvadratY']++; }elseif(isset($_POST['flyttaVänster'])){ $_SESSION['kvadratX']--; }elseif(isset($_POST['flyttaHöger'])){ $_SESSION['kvadratX']++; } // Visa spelpanen (en tabell på 5*5 celler) echo '<table border="1">'; for($y=1; $y<=5; $y++){ echo '<tr>'; for($x=1; $x<=5; $x++){ echo '<td style="width: 50px; height: 50px;'; // Om fyrkanten har denna positionen... if($y == $_SESSION['kvadratY'] && $x == $_SESSION['kvadratX']){ // ...färga cellen svart echo 'background-color: black;'; } echo '"></td>'; } echo '</tr>'; } echo '</table>'; ?> <!-- Formulär med knappar för att flytta kvadraten. --> <form method="post" action=""> <input type="submit" name="flyttaUpp" value="Uppåt"><br> <input type="submit" name="flyttaVänster" value="Vänster"> <input type="submit" name="flyttaHöger" value="Höger"><br> <input type="submit" name="flyttaNer" value="Neråt"> </form> </body> </html>

Koden ovan fungerar bra, men den är inte särskilt bra struktuerad. Ett vanligt mål att sträva efter när man programmerar är att försöka bryta ut layout-delen från övriga delen av programmet. Om man senare vill ändra programmets layout behöver man enbart titta på delen av koden som hand om den biten - man behöver inte sätta sig in i hela koden. I exemplet ovan börjat layout-delen på rad 7 och slutar på rad 86. Om man vill ändra en liten sak i layouten (till exempel ändra kvadraten färg från svart till röd) vet man alltså inte vart i koden man ska börja leta, men om man lyckas bryta ut all kod som är relaterad till layouten så har man strukturerat upp den på ett bra sätt. I exemplet nedan har vi gjort detta.

<?php session_start(); if(!isset($_SESSION['kvadratX'])){ $_SESSION['kvadratX'] = 3; $_SESSION['kvadratY'] = 3; } if(isset($_POST['flyttaUpp'])){ $_SESSION['kvadratY']--; }elseif(isset($_POST['flyttaNer'])){ $_SESSION['kvadratY']++; }elseif(isset($_POST['flyttaVänster'])){ $_SESSION['kvadratX']--; }elseif(isset($_POST['flyttaHöger'])){ $_SESSION['kvadratX']++; } // Nu börjar layout-delen ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Flytta kvadraten!</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> </head> <body> <h1>Detta är ett spel där du kan flytta en kvadrat.</h1> <?php echo '<table border="1">'; for($y=1; $y<=5; $y++){ echo '<tr>'; for($x=1; $x<=5; $x++){ echo '<td style="width: 50px; height: 50px;'; if($y == $_SESSION['kvadratY'] && $x == $_SESSION['kvadratX']){ echo 'background-color: black;'; } echo '"></td>'; } echo '</tr>'; } echo '</table>'; ?> <form method="post" action=""> <input type="submit" name="flyttaUpp" value="Uppåt"><br> <input type="submit" name="flyttaVänster" value="Vänster"> <input type="submit" name="flyttaHöger" value="Höger"><br> <input type="submit" name="flyttaNer" value="Neråt"> </form> </body> </html> <?php // Nu är layout-delen slut ?>

Nu har vi lyckats isolera layout-delen, så om man vill ändra spelets layout behöver man enbart bekymra sig om kodens nedre del, och om man vill ändra spelets funktionalitet/regler behöver man enbart bekymra sig om kodens övre del.

Det är just det här designmönstret MVC handlar om - att strukturera upp koden genom att gruppera den i relevanta delar. MVC står för Model View Controller, som på svenska blir Modell Vy Kontroll. Vad de kallar Vy är det vi tidigare kallade layout, men de avser exakt samma sak.

I exemplet ovan har vi delat upp koden i två olika delar: en layout-del, och en del som innehåller all övrig kod. I MVC tar man dock det hela ett steg längre, och delar även upp den övriga koden i två separata delar (som såklart går under namnen Modell och Kontroll). Modell-delen innehåller all data och funktionalitet som behövs för att programmet ska fungera, medan Kontroll-delen ser till att programmet fungerar som det ska. I vårt exempelspel skulle alltså Modell-delen bestå av $_SESSION['kvadratX'], $_SESSION['kvadratY'] samt funktioner som ändrar dessa variablers värden, och Kontroll-delen all övrig kod (villkorssatserna ovan layout-delen).

Passande nog är vår icke-layout-kod mer eller mindre redan uppdelad i dessa två delar. Se kommentarerna i nedanstående kod.

<?php session_start(); // Kontroll-kod som styr... if(!isset($_SESSION['kvadratX'])){ // ...när en ny modell ska skapas (och till vad) $_SESSION['kvadratX'] = 3; $_SESSION['kvadratY'] = 3; } // Kontroll-kod som styr... if(isset($_POST['flyttaUpp'])){ // ...när saker ska flyttas (och hur) $_SESSION['kvadratY']--; }elseif(isset($_POST['flyttaNer'])){ $_SESSION['kvadratY']++; }elseif(isset($_POST['flyttaVänster'])){ $_SESSION['kvadratX']--; }elseif(isset($_POST['flyttaHöger'])){ $_SESSION['kvadratX']++; } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Flytta kvadraten!</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> </head> <body> <h1>Detta är ett spel där du kan flytta en kvadrat.</h1> <?php echo '<table border="1">'; for($y=1; $y<=5; $y++){ echo '<tr>'; for($x=1; $x<=5; $x++){ echo '<td style="width: 50px; height: 50px;'; if($y == $_SESSION['kvadratY'] && $x == $_SESSION['kvadratX']){ echo 'background-color: black;'; } echo '"></td>'; } echo '</tr>'; } echo '</table>'; ?> <form method="post" action=""> <input type="submit" name="flyttaUpp" value="Uppåt"><br> <input type="submit" name="flyttaVänster" value="Vänster"> <input type="submit" name="flyttaHöger" value="Höger"><br> <input type="submit" name="flyttaNer" value="Neråt"> </form> </body> </html>

I ObjektOrienterad Programmering blir denna uppdelning väldigt tydlig när man följer MVC-mönstret.

<?php // Detta är vår Modell över en position på spelplanen (fyrkantens position) class Position{ private $x; private $y; public function __construct($x, $y){ $this->x = $x; $this->y = $y; } public function flyttaSidled($antalSteg){ $this->x += $antalSteg; } public function flyttaHöjdled($antalSteg){ $this->y += $antalSteg; } public function hämtaX(){ return $this->x; } public function hämtaY(){ return $this->y; } } ?>
<?php include_once 'Position.php'; // Detta är vår Vy, som bestämmer hur spelet ska visas class VisaSpelplan{ private $kvadratsPosition; public function __construct($kvadratsPosition){ $this->kvadratsPosition = $kvadratsPosition; } public function hämtaSomHtml(){ return ' <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Flytta kvadraten!</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> </head> <body> <h1>Detta är ett spel där du kan flytta en kvadrat.</h1> '.$this->hämtaTabellSomHtml().' '.$this->hämtaFormulärSomHtml().' </body> </html> '; } private function hämtaTabellSomHtml(){ $tabell = '<table border="1">'; for($y=1; $y<=5; $y++){ $tabell .= '<tr>'; for($x=1; $x<=5; $x++){ $tabell .= '<td style="width: 50px; height: 50px;'; if($y == $this->kvadratsPosition->hämtaY() && $x == $this->kvadratsPosition->hämtaX()){ $tabell .= 'background-color: black;'; } $tabell .= '"></td>'; } $tabell .= '</tr>'; } $tabell .= '</table>'; return $tabell; } private function hämtaFormulärSomHtml(){ return ' <form method="post" action=""> <input type="submit" name="flyttaUpp" value="Uppåt"><br> <input type="submit" name="flyttaVänster" value="Vänster"> <input type="submit" name="flyttaHöger" value="Höger"><br> <input type="submit" name="flyttaNer" value="Neråt"> </form> '; } } ?>
<?php /* Hela denna filen är kontrollen. Det är denna filen som kör spelet. */ include_once 'Position.php'; include_once 'VisaSpelplan.php'; session_start(); // Skapa en ny modell av kvadratens position, om den inte finns if(!isset($_SESSION['kvadratsPosition'])){ $_SESSION['kvadratsPosition'] = new Position(3, 3); } // Flytta kvadraten, om besökaren har beordrat det if(isset($_POST['flyttaUpp'])){ $_SESSION['kvadratsPosition']->flyttaHöjdled(-1); }elseif(isset($_POST['flyttaNer'])){ $_SESSION['kvadratsPosition']->flyttaHöjdled(1); }elseif(isset($_POST['flyttaVänster'])){ $_SESSION['kvadratsPosition']->flyttaSidled(-1); }elseif(isset($_POST['flyttaHöger'])){ $_SESSION['kvadratsPosition']->flyttaSidled(1); } // Visa spelplanen $vy = new VisaSpelplan($_SESSION['kvadratsPosition']); echo $vy->hämtaSomHtml(); ?>