【軟件工程】一文搞懂 MVC、MVP、MVVM 架構
文章目錄
- 【軟件工程】一文搞懂 MVC、MVP、MVVM 架構
- 1 為什麼會有這些模式
- 2 MV* 系列架構定義
- 2.1 MVC(Model–View–Controller)
- 2.2 MVP(Model–View–Presenter)
- 2.3 MVVM(Model–View–ViewModel)
- 3 代碼示例
- 3.1 MVC(Node.js/Express 服務端渲染)
- 3.2 MVP(Android 偽代碼)
- 3.3 MVVM(前端/Android 思路相通)
- 4 選用建議
- 5 常見誤區
- 6 進階架構簡介
- 7 遷移與落地建議
- 8 Checklists(落地自檢表)
- 9 總結
1 為什麼會有這些模式
當應用變複雜後,展示(UI)、業務(邏輯/規則)、數據(存儲/網絡)揉在一起會導致可讀性差、測試困難、UI 框架升級(Android/iOS/Web)時牽動業務大面積重寫等問題。
MV* 系列就是把職責“拆開、解耦、可替換”,讓你能獨立開發、獨立測試、獨立演進。
2 MV* 系列架構定義
2.1 MVC(Model–View–Controller)
- Model:數據與業務規則(實體、倉庫、服務)。
- View:界面展示(模板、XML、HTML)。
- Controller:接收輸入/路由,協調 Model & View。
- 特點:Controller 常與 View 互動頻繁,容易演變成“胖 Controller”。
2.2 MVP(Model–View–Presenter)
- View:純展示 + 用户事件回調(儘量不包含業務)。
- Presenter:拿數據、組裝展示模型、驅動 View 更新(通常通過接口)。
- Model:數據/業務。
- 特點:可單測 Presenter;但容易出現“胖 Presenter”。
2.3 MVVM(Model–View–ViewModel)
- View:綁定 ViewModel(數據驅動 UI)。
- ViewModel:暴露可觀察數據(Observable/LiveData/StateFlow),不持有 View 引用。
- Model:數據/業務。
- 特點:數據綁定/響應式,減少樣板代碼;需小心雙向綁定和狀態同步複雜度。
MVC、MVP、MVVM 架構的對比如下:
|
維度
|
MVC
|
MVP
|
MVVM
|
|
UI 更新方式
|
Controller 驅動
|
Presenter 命令式調用 View
|
綁定/訂閲,View 被動更新
|
|
可測試性
|
中等(易胖 C)
|
好(Presenter 易單測)
|
好(VM 易單測)
|
|
學習/樣板
|
低
|
中
|
中-高(需理解響應式/綁定)
|
|
常見風險
|
Massive Controller
|
Massive Presenter
|
綁定濫用、狀態混亂
|
|
典型場景
|
服務端模板渲染、早期 iOS
|
Android 老項目、無數據綁定框架
|
Android Jetpack / SwiftUI / Vue / React(配合狀態管理)
|
3 代碼示例
3.1 MVC(Node.js/Express 服務端渲染)
// controller
app.get('/posts/:id', async (req, res) => {
const post = await PostRepo.find(req.params.id); // Model
res.render('post.ejs', { post }); // View
});
3.2 MVP(Android 偽代碼)
// View 接口
interface PostView {
fun showLoading()
fun showPost(vm: PostVM)
fun showError(msg: String)
}
// Presenter
class PostPresenter(
private val view: PostView,
private val repo: PostRepository
) {
suspend fun load(id: String) {
view.showLoading()
runCatching { repo.find(id) }
.onSuccess { view.showPost(PostVM.from(it)) }
.onFailure { view.showError(it.message ?: "error") }
}
}
3.3 MVVM(前端/Android 思路相通)
Vue(簡化版):
<div id="app">
<div v-if="loading">Loading...</div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
<script>
new Vue({
el: '#app',
data: () => ({ loading: false, post: {} }),
async created() {
this.loading = true
try { this.post = await api.getPost(1) }
finally { this.loading = false }
}
})
</script>
Android(ViewModel + LiveData/StateFlow 思路):
class PostVM(private val repo: PostRepository): ViewModel() {
val uiState = MutableStateFlow(UiState())
fun load(id: String) = viewModelScope.launch {
uiState.update { it.copy(loading = true) }
runCatching { repo.find(id) }
.onSuccess { uiState.update { it.copy(loading=false, post=it) } }
.onFailure { uiState.update { it.copy(loading=false, error="error") } }
}
}
4 選用建議
- 後端服務端渲染(Java/Spring MVC、Rails、Django):MVC 足夠清晰。
- 傳統 Android 老項目:MVP 易於在不引入數據綁定的前提下改善可測性。
- 現代客户端(Android Jetpack、SwiftUI、前端 Vue/React/Svelte):MVVM/響應式更順手;再配單向數據流(Redux/MVI)管理複雜狀態。
- 團隊基礎薄弱/交付火急:先選最熟悉且能寫出單測的;從“分層 + 接口隔離 + 狀態集中”做起。
5 常見誤區
- “胖”層問題
- MVC 的 Controller、MVP 的 Presenter、MVVM 的 ViewModel 都可能變“上帝類”。
- 解決:用 UseCase/Interactor 拆業務;ViewModel 只做狀態編排。
- View 持有 Presenter/VM 的生命週期泄漏
- Android 上注意解除引用;優先使用弱引用/基於生命週期的協程或流。
- 雙向綁定濫用
- 小心意外循環更新、難以追蹤的副作用。複雜場景建議單向數據流。
- 跨層依賴
- View/VM 不要直接依賴數據源細節(網絡、DB),統一通過 Repository/UseCase。
- 狀態分散
- 一個頁面唯一真相源(Single Source of Truth):集中到 ViewModel/Store。
6 進階架構簡介
- Flux/Redux(單向數據流):Action → Reducer → Store → View,適合前端複雜交互;可時光旅行調試。
- MVI(Model–View–Intent):意圖驅動、狀態不可變,Android/Compose 常見。
- VIPER(iOS):View、Interactor、Presenter、Entity、Router,職責最細但樣板多。
- Clean Architecture:以業務為中心(UseCase/Interactor),外層適配框架,配合 MVVM/MVI 很穩。
7 遷移與落地建議
- 先立分層:UI 層、領域/用例層、數據層,畫出依賴方向(只依賴內向)。
- 抽接口:Repository、UseCase 先抽出來,UI 不直觸網/庫。
- 引入狀態容器:MVVM 就讓 ViewModel 成為單一狀態源;複雜再上 Redux/MVI。
- 把副作用關起來:網絡/存儲/導航等放在 UseCase/Effect 層統一處理。
- 單測優先:先測 UseCase/Repository,再測 Presenter/ViewModel。
- 分階段替換:新頁面用新架構;老頁面“外殼不動、內核替換”。
8 Checklists(落地自檢表)
- 通用
-
- View 是否只做渲染與轉發用户意圖?
- 業務是否沉到 UseCase/Interactor?
- 是否存在跨層直接依賴?有則打回。
- 是否有單一狀態源(SSOT)?
- MVP 專項
-
- Presenter 是否無 Android/UI 依賴、可純單測?
- View 與 Presenter 生命週期解綁是否到位?
- MVVM 專項
-
- ViewModel 不持有 View/Context?
- 狀態不可變(data class/Reducer 風格)?
- 綁定是否可能引起更新迴路?
9 總結
MVC、MVP、MVVM 沒有絕對“最好”,只有團隊當前認知/工具鏈/業務複雜度下的“最合適”。掌握它們的職責邊界與數據流思想,再配合分層與單向數據流,就能避免“越寫越亂”的命運。