MVC — Model-View-Controller
Introduktion
MVC är ett av de mest använda arkitekturmönstren inom webbutveckling. Det delar upp en applikation i tre sammankopplade delar med tydliga ansvarsområden — vilket gör koden lättare att underhålla, testa och vidareutveckla.
De tre delarna
Model — Datalagret
Modellen representerar applikationens data och affärslogik. Den är helt oberoende av hur data presenteras.
Ansvar:
- Hämta och spara data (databas, API)
- Validera indata
- Tillämpa affärsregler
- Notifiera View om förändringar (via Observer i klassisk MVC)
View — Presentationslagret
View ansvarar för att presentera data för användaren. Den innehåller ingen affärslogik — bara renderingslogik.
Ansvar:
- Rendera HTML/UI baserat på data från Modellen
- Ta emot och vidarebefordra användarinteraktioner till Controller
- Uppdatera sig när Modellen ändras
Controller — Koordinatorn
Controller är limmet som håller ihop Model och View. Den tar emot användarens input, bearbetar den och avgör vad som ska hända.
Ansvar:
- Ta emot HTTP-förfrågningar
- Validera och tolka indata
- Anropa rätt metoder på Modellen
- Bestämma vilken View som ska renderas
Dataflöde
Användare → View → Controller → Model → Controller → View → Användare
- Användaren klickar på “Visa alla produkter”
- Controllern tar emot HTTP GET
/products - Controllern anropar
Product::getAll()på Modellen - Modellen hämtar data från databasen och returnerar den
- Controllern skickar data till View
products/index.html - View renderar HTML med produktlistan
- Svaret skickas tillbaka till användaren
Exempel — Produktsystem (PHP / Laravel-stil)
<?php
// ── Model ─────────────────────────────────────────────────
class Product
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly float $price
) {}
// Hämtar alla produkter från array "databasen"
public static function getAll(): array
{
return [ // Hårdkodad data för exemplet
new self(1, 'Laptop Pro', 14999.00),
new self(2, 'Trådlös Mus', 399.00),
new self(3, 'Mekaniskt tangentbord', 1299.00),
];
}
public static function findById(int $id): ?self
{
foreach (self::getAll() as $product) {
if ($product->id === $id) return $product;
}
return null;
}
}
// ── View (template) ───────────────────────────────────────
class ProductView
{
public function renderList(array $products): string
{
$rows = array_map(fn($p) =>
"<tr><td>{$p->id}</td><td>{$p->name}</td><td>{$p->price} kr</td></tr>",
$products
);
return
"<table><thead><tr><th>ID</th><th>Namn</th><th>Pris</th></tr></thead>"
. "<tbody>" . implode('', $rows) . "</tbody></table>";
}
public function renderDetail(Product $product): string
{
return "<h1>{$product->name}</h1><p>Pris: {$product->price} kr</p>";
}
public function renderNotFound(): string
{
return "<p>Produkten hittades inte.</p>";
}
}
// ── Controller ────────────────────────────────────────────
class ProductController
{
public function __construct(private ProductView $view) {}
// GET /products
public function index(): string
{
$products = Product::getAll();
return $this->view->renderList($products);
}
// GET /products/{id}
public function show(int $id): string
{
$product = Product::findById($id);
if ($product === null) {
http_response_code(404);
return $this->view->renderNotFound();
}
return $this->view->renderDetail($product);
}
}
// ── Router / Bootstrap ────────────────────────────────────
$controller = new ProductController(new ProductView());
// Simulera en HTTP-förfrågan
$uri = $_SERVER['REQUEST_URI'] ?? '/products';
if ($uri === '/products') {
echo $controller->index();
} elseif (preg_match('#^/products/(\d+)$#', $uri, $m)) {
echo $controller->show((int) $m[1]);
}
MVC i moderna ramverk
| Ramverk | Språk | Model | View | Controller |
|---|---|---|---|---|
| Laravel | PHP | Eloquent ORM | Blade-templates | Http/Controllers/ |
| Spring MVC | Java | JPA / Hibernate | Thymeleaf / JSP | @Controller |
| ASP.NET MVC | C# | Entity Framework | Razor Pages | Controller |
| Ruby on Rails | Ruby | Active Record | ERB | ApplicationController |
Fördelar
- Separation of Concerns — Varje del har ett tydligt ansvar.
- Testbarhet — Modellen kan testas utan UI; Controller utan databas.
- Parallell utveckling — Frontend och backend kan arbeta oberoende.
- Återanvändbarhet — Samma Model kan visas av flera Views (web, cli osv).
Nackdelar
- Kan vara overkill för enkla applikationer med lite logik.
- Controller kan bli överfull (Fat Controller) om man inte är disciplinerad.
- Kan leda till tätt kopplad View-Controller i klassisk implementering.
Av Victor Hernandez från Bytebase.se