【軟件工程】一文搞懂 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”。

深入淺出 MVC、MVVM、MVP_#mvc


2.2 MVP(Model–View–Presenter)

  • View:純展示 + 用户事件回調(儘量不包含業務)。
  • Presenter:拿數據、組裝展示模型、驅動 View 更新(通常通過接口)。
  • Model:數據/業務。
  • 特點:可單測 Presenter;但容易出現“胖 Presenter”。

深入淺出 MVC、MVVM、MVP_#mvc_02


2.3 MVVM(Model–View–ViewModel)

  • View:綁定 ViewModel(數據驅動 UI)。
  • ViewModel:暴露可觀察數據(Observable/LiveData/StateFlow),不持有 View 引用。
  • Model:數據/業務。
  • 特點數據綁定/響應式,減少樣板代碼;需小心雙向綁定和狀態同步複雜度。


深入淺出 MVC、MVVM、MVP_#mvc_03


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 遷移與落地建議

  1. 先立分層:UI 層、領域/用例層、數據層,畫出依賴方向(只依賴內向)。
  2. 抽接口:Repository、UseCase 先抽出來,UI 不直觸網/庫。
  3. 引入狀態容器:MVVM 就讓 ViewModel 成為單一狀態源;複雜再上 Redux/MVI。
  4. 把副作用關起來:網絡/存儲/導航等放在 UseCase/Effect 層統一處理。
  5. 單測優先:先測 UseCase/Repository,再測 Presenter/ViewModel。
  6. 分階段替換:新頁面用新架構;老頁面“外殼不動、內核替換”。

8 Checklists(落地自檢表)

  • 通用

  • View 是否只做渲染與轉發用户意圖?
  • 業務是否沉到 UseCase/Interactor?
  • 是否存在跨層直接依賴?有則打回。
  • 是否有單一狀態源(SSOT)?
  • MVP 專項

  • Presenter 是否無 Android/UI 依賴、可純單測?
  • View 與 Presenter 生命週期解綁是否到位?
  • MVVM 專項

  • ViewModel 不持有 View/Context?
  • 狀態不可變(data class/Reducer 風格)?
  • 綁定是否可能引起更新迴路?

9 總結

MVC、MVP、MVVM 沒有絕對“最好”,只有團隊當前認知/工具鏈/業務複雜度下的“最合適”。掌握它們的職責邊界與數據流思想,再配合分層與單向數據流,就能避免“越寫越亂”的命運。