
概念概覽
flowchart TD User["使用者"] -->|發送請求| Controller["Controller<br/>處理請求與協調"] Controller -->|操作資料| Model["Model<br/>資料與商業邏輯"] Model -->|回傳資料| View["View<br/>使用者介面呈現"] View -->|回應畫面| User style User fill:#f96,stroke:#333,stroke-width:2px style Controller fill:#bbf,stroke:#333 style Model fill:#bfb,stroke:#333 style View fill:#fbb,stroke:#333
什麼是 MVC?
想像你走進一間餐廳:菜單(View) 是你看到的東西,廚房(Model) 負責處理食材和料理邏輯,而服務生(Controller) 接收你的點單、把需求傳給廚房、再把做好的菜端到你面前。你不會自己跑進廚房炒菜,廚房也不需要知道菜單長什麼樣——每個角色各司其職。MVC 的精神就是這樣:把「資料」「畫面」「流程控制」拆開來,讓程式碼更好維護。
MVC 是 Model-View-Controller 的縮寫,是一種軟體架構模式。它將應用程式分為三個核心元件,各自負責不同的職責:
| 元件 | 職責 | 範例 |
|---|---|---|
| Model | 資料與商業邏輯 | 資料庫操作、驗證規則 |
| View | 使用者介面呈現 | HTML 模板、JSON 回應 |
| Controller | 處理請求與協調 | 路由處理、呼叫 Model |
┌─────────────────────────────────────────────────────────┐
│ │
│ 使用者 ──請求──> Controller ──操作──> Model │
│ ↑ │ │ │
│ │ │ │ │
│ └───回應───── View <──取得資料────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
MVC 的優點
-
關注點分離(Separation of Concerns)
- 每個元件專注於自己的職責
- 修改 UI 不影響商業邏輯
-
提高可測試性
- Model 可以獨立進行單元測試
- 不需要啟動整個應用程式
-
便於團隊協作
- 前端工程師專注 View
- 後端工程師專注 Model 和 Controller
-
程式碼重用
- 同一個 Model 可以被不同的 View 使用
- API 和網頁可以共用邏輯
MVC 實作範例
Express.js 實作
Model(models/user.js)
import db from '../database.js';
export const User = {
async findById(id) {
return db.query('SELECT * FROM users WHERE id = ?', [id]);
},
async create(data) {
const { name, email } = data;
return db.query(
'INSERT INTO users (name, email) VALUES (?, ?)',
[name, email]
);
},
async update(id, data) {
const { name, email } = data;
return db.query(
'UPDATE users SET name = ?, email = ? WHERE id = ?',
[name, email, id]
);
},
};Controller(controllers/userController.js)
import { User } from '../models/user.js';
export const userController = {
async show(req, res) {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).render('error', { message: '使用者不存在' });
}
res.render('user/show', { user });
},
async create(req, res) {
const { name, email } = req.body;
// 驗證
if (!name || !email) {
return res.status(400).render('user/new', {
error: '請填寫所有欄位',
});
}
const user = await User.create({ name, email });
res.redirect(`/users/${user.id}`);
},
};View(views/user/show.ejs)
<!DOCTYPE html>
<html>
<head>
<title><%= user.name %> - 個人資料</title>
</head>
<body>
<h1><%= user.name %></h1>
<p>Email: <%= user.email %></p>
<a href="/users/<%= user.id %>/edit">編輯</a>
</body>
</html>Routes(routes/users.js)
import express from 'express';
import { userController } from '../controllers/userController.js';
const router = express.Router();
router.get('/:id', userController.show);
router.post('/', userController.create);
export default router;OK,理論講完了,程式碼也看過了。接下來聊聊一些你在實務上會碰到的爭議——MVC 不是銀彈,它也有被誤用和被挑戰的地方。
MVC 的誤解與爭議
「MVC 是一個巨大誤會」
有人認為現今 Web 開發中的 MVC 與最初 Smalltalk 的 MVC 已經不同:
-
原始 MVC(1970s Smalltalk)
- View 直接監聽 Model 的變化(Observer Pattern)
- Controller 處理使用者輸入
-
Web MVC
- View 不直接監聽 Model
- Controller 同時處理輸入和協調回應
這種演變是因為 Web 的 Request-Response 模式與桌面應用的持續互動模式不同。
Controller 太肥?
常見問題是 Controller 承擔太多責任,變成「Fat Controller」。解決方案:
- Service Layer:將商業邏輯抽到 Service
- Repository Pattern:將資料存取邏輯抽出 Model
- Form Objects:處理表單驗證
// 使用 Service 避免 Fat Controller
class UserController {
async create(req, res) {
const result = await userService.register(req.body);
if (result.error) {
return res.status(400).json({ error: result.error });
}
res.status(201).json(result.user);
}
}MVC 的變體
| 模式 | 說明 | 常見框架 |
|---|---|---|
| MVC | 經典模式 | Rails, Laravel, Spring MVC |
| MVP | Presenter 取代 Controller | Android(早期) |
| MVVM | ViewModel 與 View 雙向綁定 | Vue.js, Angular |
| Flux/Redux | 單向資料流 | React + Redux |
現代前端與 MVC
現代前端框架(React、Vue、Svelte)已經不完全遵循傳統 MVC:
- Component-Based:View 和部分邏輯合在元件中
- State Management:使用 Redux、Pinia 管理狀態
- API-First:前後端分離,後端只提供 API
但 MVC 的核心精神——關注點分離——依然是重要的設計原則。
延伸閱讀
MVC是一個巨大誤會 跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR 架構 後端 MVP 設計原則 前端 MVP 設計原則