mvvm模式和mvc的区别_Android 开发中的架构模式 -- MVC / MVP / MVVM
預備知識
了解 Android 基本開發
看完本文可以達到什么程度
了解如何分析一個架構模式掌握 MVC,MVP,MVVM 架構定義和實現
更多面試內容,面試專題,flutter視頻 全套,音視頻從0到高手開發。
關注GitHub:https://github.com/xiangjiana/Android-MS免費獲取面試PDF合集免費提供簡歷修改建議,獲取大廠面試PDF和視頻教程
文章概覽
一、什么是架構
關于架構的定義,其實在很多書籍和文章中都是不同的,很難做一個統一。這里列舉兩個定義:
在維基百科里是這樣定義的:
軟件架構是一個系統的草圖。軟件架構描述的對象是直接構成系統的抽象組件。各個組件之間的連接則明確和相對細致地描述組件之間的通訊。在實現階段,這些抽象組件被細化為實際的組件,比如具體某個類或者對象。
在 IEEE 軟件工程標準詞匯中是這樣定義的:
架構是以組件、組件之間的關系、組件與環境之間的關系為內容的某一系統的基本組織結構,以及指導上述內容設計與演化的原理。
在看過茫茫多的架構定義以后,我理解的架構是這樣的:
1.為了解決特定的問題而提出2.按照特定的原則將系統整體進行模塊/組件/角色的劃分
3.建立模塊/組件/角色間的溝通機制
具體解釋一下,首先是要有特定的問題,沒有問題空談架構,仿佛是空中樓閣,沒有實用價值,而對應到不同的問題,會有不同的解決方式。
其次是模塊的劃分要根據特定的原則,沒有原則隨意劃分,也就無從去評估一個架構的好壞。最后就是模塊間的通信機制,讓系統成為一個整體.
最后,架構模式,其實更多的是一種思想,一種規則,往往一種架構模式可能會有不同的實現方式,而實現方式之間,只有合適與否,并沒有對錯之分。
二、如何分析一種架構模式
上面我們介紹了架構的定義,根據這個定義,我們在后面分析架構模式的時候,也會從這三方面進行。
2.1.架構解決了什么問題
知道了架構模式要解決的問題,我們才能針對性的去看,去想其解決方法是否得當,是否合適。
2.2.架構模式是如何劃分角色的
架構中最重要的就是角色 / 模塊的劃分,理解了架構模式中的角色劃分,才能更好的理解其結構。
2.3.角色間是如何通信的
角色間的通信也是重要的。相同的角色劃分,采用不同的通信方式,往往就構成了不同的架構模式。
角色間通信我們可以理解為數據的流向。在 Android 開發中,通信中的數據可以理解為兩種,一種是數據結構,也就是網絡請求,本地存儲等通信使用的 JavaBean,另一種是事件,也就是控件產生的動作,包括觸摸,點擊,滑動等等。我們在通信過程中,也主要關注這兩種數據。
三、常見的架構模式有哪些
對于我們 Android 開發者來說,常見的架構模式基本上就是 MVC,MVP,MVVM,這三種也是開發 GUI 應用程序常見的模式。
除此之外還有 分層模式,客戶端-服務器模式(CS模式),主從模式,管道過濾器模式,事件總線模式 等等。
這篇文章還是具體分析 MVC,MVP,MVVM 這三種架構模式。
四、不使用架構之前 App 是怎么開發的
我們在了解架構的定義以后,可能會想,為什么要用這些架構模式呢?在我們不了解這些模式之前,也是一樣的開發。類似設計模式,其實架構模式的目的不是為了讓應用軟件開發出來,而是讓結構更清晰,分工更明確,擴展更方便等等。
我們可以看看,在不使用架構模式之前我們是怎么開發的。
舉個簡單的栗子,我們界面上有 EditText,TextView,Button 三個控件,要實現的功能也比較簡單:
2.處理用戶輸入的數據
3.數據處理后輸出到 TextView 中
4.點擊 Button 清空用戶的輸入
界面如下:
我們看看不使用架構模式是怎么開發的,也就是我們一般常用的開發方式:
4.1. 首先在 xml 設計界面
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="10dp"tools:context=".MainActivity"><TextViewandroid:id="@+id/titleText"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Normal" /><EditTextandroid:id="@+id/edit"android:layout_width="match_parent"android:layout_height="50dp"android:textColor="@android:color/darker_gray" /><TextViewandroid:id="@+id/msgText"android:layout_width="wrap_content"android:layout_height="30dp"android:layout_marginTop="10dp"android:text="default msg"android:textColor="@android:color/darker_gray" /><TextViewandroid:id="@+id/clearText"android:layout_width="match_parent"android:layout_height="30dp"android:layout_marginTop="10dp"android:background="@color/colorPrimary"android:gravity="center"android:text="clear"android:textColor="@android:color/white" /></LinearLayout>1.在 Activity / Fragment 中獲取 View,進行事件監聽
2.通過 View 事件獲取數據后進行處理
3.設置處理后的數據給 View
代碼如下:
class NormalFragment : Fragment() {companion object {fun newInstance(): Fragment {return NormalFragment()}}private val handler: Handler = Handler()override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.architecture, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)titleText.text = "NORMAL"edit.addTextChangedListener(object : TextWatcher {override fun afterTextChanged(s: Editable?) {handleData(s.toString())}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}})clearText.setOnClickListener {edit.setText("")}}// 數據的處理,真實情況下可能是網絡請求,磁盤存取,大量計算邏輯等等private fun handleData(data: String) {if (TextUtils.isEmpty(data)) {msgText.text = "default msg"return}msgText.text = "handle data ..."handler.removeCallbacksAndMessages(null)// 延遲來模擬網絡或者磁盤操作handler.postDelayed({msgText.text = "handled data: $data"}, 3000)}}默認開發方式的缺點:
我們來分析一下上面的代碼,一個明顯的特點就是處理邏輯都集中在了 Activity / Fragment 中,不管是對 View 的操作,還是對數據的處理。帶來的問題就是 Activity / Fragment 中邏輯臃腫,后續擴展牽一發而動全身。而且職責劃分不清晰,給后續維護也帶來了困難。
既然如此,我們看看使用架構模式改造后是什么樣子的。
五、MVC 架構
5.1 模式介紹
其實關于 MVC 架構,在不同的框架里,實現會有些差別,這也正說明了架構是一種思想。我們這里選用一種比較主流的實現。
5.1.1. 解決什么問題
我們可以看到,上面不使用架構進行開發,帶來的問題是 Activity / Fragment 邏輯臃腫,不利于擴展。所以 MVC 就要解決的問題就是:控制邏輯,數據處理邏輯和界面交互耦合。
這里先插一個題外話,其實我們作為程序員,寫代碼不僅要實現需求,還要讓代碼易讀,易擴展。這一點,往往也能體現功力,并不是說使用了各種奇技淫巧才是大神。
不知道大家是否有接觸過 Java Swing 桌面應用開發,在 Java Swing 中,界面 / 控件的設置,也是用 Java 代碼來實現的,如果不采用架構,最后的結果就是控制邏輯,數據處理以及頁面展示的代碼都集中在一個類中,讀者朋友們可以想象一下,這樣的代碼簡直是難以維護
5.1.2. 如何劃分角色
為了解決上面的問題,MVC 架構里,將邏輯,數據,界面的處理劃分為三個部分,模型(Model)-視圖(View)-控制器(Controller)。各個部分的功能如下:
- Model 模型,負責數據的加載和存儲。
- View 視圖,負責界面的展示。
- Controller 控制器,負責邏輯控制。
5.1.3. 如何通信(數據的流向)
我們再看看三者之間是怎么通信的。
在介紹通信之前,我們先解釋一下通信中的數據是什么。其實在 Android 開發中,通信數據可以理解為兩種,一種是數據結構,也就是網絡請求,本地存儲等通信使用的 JavaBean,另一種是事件,也就是控件產生的動作,包括觸摸,點擊,滑動等等。我們在通信過程中,也主要關注這兩種數據。
在 MVC 架構中,View 產生事件,通知到 Controller,Controller 中進行一系列邏輯處理,之后通知給 Model 去更新數據,Model 更新數據后,再將數據結構通知給 View 去更新界面。
這就是一個完整 MVC 的數據流向
5.2 在 Android 中的具體實現
理解了 MVC 模式,我們看看其具體實現。
其實在 Android 開發中,其本身默認可以理解為 MVC 結構,把 View 放在 xml中與 Java 代碼解耦,然后 Activity / Fragment 充當 Controller 進行邏輯控制,但是 Android 本身并沒有對 Model進行劃分,所以往往我們會讓 Activity / Fragment 充當 Model 和 Controller 兩個角色。而且往往 xml 中的 View 操作也是在 Activity / Fragment 中,導致有時候 Activity / Fragment 也會充當一些 View 的角色。
所以我們在具體實現過程中,要把職責劃分清楚,這里我們讓 Fragment 充當 View 的角色,把 Model 和 Controller 的邏輯劃分清楚。
我們先定義三個接口如下:
// 數據模型接口,定義了數據模型的操作interface IModel {fun setView(view: IView)// 數據模型處理輸入的數據fun handleData(data: String)// 清空數據fun clearData()}// 視圖接口,定義視圖的操作interface IView {fun setController(controller: IController)// 數據處理中狀態fun dataHanding()// 數據處理完成,更新界面fun onDataHandled(data: String)}// 控制器接口,定義控制器的邏輯interface IController {fun setModel(model: IModel)// EditText 數據變化,通知控制器fun onDataChanged(data: String)// 清空按鈕點擊事件fun clearData()}上面三個接口分別定義了 Model,View,Controller的操作。有一點注意的是,根據 MVC 的通信流程,View 需要持有 Controller,Controller 需要持有 Model,Model 需要持有 View,所以需要暴露相應的接口。
下面我們看看具體的實現:
- Model 的實現
Model 中對數據的處理是添加了 "handled data: " 前綴,并增加了 3 秒的延遲
class HandleModel : IModel {private var view: IView? = nullprivate val handler: Handler = Handler(Looper.getMainLooper())override fun setView(view: IView) {this.view = view}// 接受到數據后,進行處理,這里設置了 3 秒的延遲,模擬網絡請求處理數據的操作override fun handleData(data: String) {if (TextUtils.isEmpty(data)) {return}view?.dataHanding()handler.removeCallbacksAndMessages(null)// 延遲來模擬網絡或者磁盤操作handler.postDelayed({// 數據處理完成,通知 View 更新界面view?.onDataHandled("handled data: $data")}, 3000)}// 接收到清空數據的事件,直接清空數據override fun clearData() {handler.removeCallbacksAndMessages(null)// 數據清空后,通知 View 更新界面view?.onDataHandled("")}}- Controller 的實現
Controller 的實現比較簡單,將操作直接轉發給 Model,實際上,對于復雜的業務場景,這里要處理很多業務邏輯。
class HandleController : IController {private var model: IModel? = nulloverride fun onDataChanged(data: String) {model?.handleData(data)}override fun clearData() {model?.clearData()}override fun setModel(model: IModel) {}}- View 的實現
這里 Fragment 充當了 View 的角色,主要負責將 View 的事件傳遞給 Controller,以及接受到 Model 的數據進行界面更新。
class MVCFragment : Fragment(), IView {companion object {fun newInstance(): Fragment {return MVCFragment()}}private val model: IModel = HandleModel()private var controller: IController = HandleController()override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.architecture, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)setController(controller)model.setView(this)titleText.text = "MVC"edit.addTextChangedListener(object : TextWatcher {override fun afterTextChanged(s: Editable?) {// 通知 Controller 輸入的數據產生變化controller?.onDataChanged(s.toString())}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}})clearText.setOnClickListener {// 通知 Controller 清空數據事件controller?.clearData()}}// Model 數據變化,進行界面更新override fun onDataHandled(data: String) {if (TextUtils.isEmpty(data)) {edit.setText("")msgText.text = "default msg"} else {msgText.text = data}}// Model 數據變化,進行界面更新override fun dataHanding() {msgText.text = "handle data ..."}override fun setController(controller: IController) {this.controller = controller}}這樣我們就實現了一個簡單的 MVC 結構。
5.3 MVC 架構模式的優缺點
優點:
- 結構清晰,職責劃分清晰
- 降低耦合
- 有利于組件重用
缺點:
- 其實我們上述的示例,已經是經過優化的 MVC 結構了,一般來說,Activity / Fragment 會承擔 View 和 Controller 兩個角色,就會導致 Activity / Fragment 中代碼較多
- Model 直接操作 View,View 的修改會導致 Controller 和 Model 都進行改動
- 增加了代碼結構的復雜性
六、MVP 架構
6.1 模式介紹
6.1.1. 解決什么問題
MVP 要解決的問題和 MVC 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,同時能將 MVC中的 View 和 Model 解耦。
6.1.2. 如何劃分角色
MVP 架構里,將邏輯,數據,界面的處理劃分為三個部分,模型(Model)-視圖(View)-控制器(Presenter)。各個部分的功能如下:
- Model 模型,負責數據的加載和存儲。
- View 視圖,負責界面的展示。
- Presenter 控制器,負責邏輯控制
6.1.3. 如何通信(數據的流向)
我們可以看到,MVP 中的各個角色劃分,和 MVC 基本上相似,那么區別在哪里呢?區別就在角色的通信上。
MVP 和 MVC 最大的不同,就是 View 和 Model 不相互持有,都通過 Presenter 做中轉。View 產生事件,通知給 Presenter,Presenter中進行邏輯處理后,通知 Model 更新數據,Model 更新數據后,通知數據結構給 Presenter,Presenter 再通知 View 更新界面。
這就是一個完整 MVP 的數據流向。
6.2 在 Android 中的實現
理解了 MVP 之后,我們看一下其具體實現。
首先我們定義三個接口:
// 模型接口,定義了數據模型的操作interface IModel {fun setPresenter(presenter: IPresenter)// 梳理數據fun handleData(data: String)// 清除數據fun clearData()}// 視圖接口,定義了視圖的操作interface IView {fun setPresenter(presenter: IPresenter)// 數據處理中視圖fun loading()// 數據展示fun showData(data: String)}// 控制器,定義了邏輯操作interface IPresenter {fun setView(view: IView)fun setModel(model: IModel)// Model 處理完成數據通知 Presenterfun dataHandled(data: String)// Model 清除數據后通知 Presenterfun dataCleared()// View 中 EditText 文字變化后通知 Presenterfun onTextChanged(text: String)// View 中 Button 點擊事件通知 Presenterfun onClearBtnClicked()}上面定義了 View,Model,Presenter 三個接口,其中 View 和 Model 會持有 Presenter,Presenter持有 View和 Model。
接著看下接口的實現:
- Model 的實現
Model 的實現和前面 MVC 中的實現基本一致,不過在 MVC 中 Model 直接操作 View 進行視圖展示,而在 MVP 里,要通知 Presenter 去中轉。
- View 的實現
這里依舊是 Fragment 充當了 View 的角色,主要負責將 View 的事件傳遞給 Presenter,以及接受到 Presenter 的數據進行界面更新。
class MVPFragment : Fragment(), IView {companion object {fun newInstance(): Fragment {val presenter = Presenter()val fragment = MVPFragment()val model = HandleModel()fragment.setPresenter(presenter)model.setPresenter(presenter)presenter.setModel(model)presenter.setView(fragment)return fragment}}var mpresenter: IPresenter? = nulloverride fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.architecture, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)titleText.text = "MVP"edit.addTextChangedListener(object : TextWatcher {override fun afterTextChanged(s: Editable?) {// 傳遞 文字修改 事件給 Presentermpresenter?.onTextChanged(s.toString())}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}})clearText.setOnClickListener {// 傳遞按鈕點擊事件給 Presentermpresenter?.onClearBtnClicked()}}override fun setPresenter(presenter: IPresenter) {this.mpresenter = presenter}// 展示數據處理中的視圖override fun loading() {msgText.text = "handling data ..."}// 展示處理后的數據override fun showData(data: String) {msgText.text = data}}- Presenter 的實現
這里 Presenter 的實現比較簡單,沒有太多的業務邏輯,實際應用中,這里會進行業務邏輯的處理。
class Presenter : IPresenter {private var model: IModel? = nullprivate var view: IView? = nulloverride fun setModel(model: IModel) {this.model = model}override fun setView(view: IView) {this.view = view}override fun dataHandled(data: String) {view?.showData(data)}override fun dataCleared() {view?.showData("")}override fun onTextChanged(text: String) {view?.loading()model?.handleData(text)}override fun onClearBtnClicked() {model?.clearData()}}6.3 MVP 架構模式的優缺點
優點:
- 結構清晰,職責劃分清晰
- 模塊間充分解耦
- 有利于組件的重用
缺點:
- 會引入大量的接口,導致項目文件數量激增
- 增大代碼結構復雜性
七、MVVM 架構
7.1 模式介紹
7.1.1. 解決什么問題
MVVM 要解決的問題和 MVC,MVP 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,并且同時能將 MVC 中的 View 和 Model 解耦,還可以把 MVP 中 Presenter 和 View 也解耦。
7.1.2. 如何劃分角色
MVVM 架構里,將邏輯,數據,界面的處理劃分為三個部分,模型(Model)-視圖(View)-邏輯(ViewModel)。各個部分的功能如下:
- Model 模型,負責數據的加載和存儲。
- View 視圖,負責界面的展示。
- ViewModel 控制器,負責邏輯控制。
7.1.3. 如何通信(數據的流向)
我們可以看到,MVP 中的各個角色劃分,和 MVC,MVP 基本上相似,區別也是在于角色的通信上。
我們上面說到,在 MVP 中,就是 View 和 Model 不相互持有,都通過 Presenter 做中轉。這樣可以使 View 和 Model 解耦。
而在 MVVM 中,解耦做的更徹底,ViewModel 也不會持有 View。其中 ViewModel 中的改動,會自動反饋給 View 進行界面更新,而 View中的事件,也會自動反饋給 ViewModel。
要達到這個效果,當然要使用一些工具輔助,比較常用的就是 databinding。
在 MVVM 中,數據的流向是這樣的:
View 產生事件,自動通知給 ViewMode,ViewModel 中進行邏輯處理后,通知 Model 更新數據,Model 更新數據后,通知數據結構給 ViewModel,ViewModel 自動通知 View 更新界面。
這就是一個完整 MVVM 的數據流向。
7.2 在 Android 中的實現
MVVM 的實現會復雜一點,我們先看下接口的定義:
// ViewModel 接口,定義了邏輯操作interface IViewModel {fun setModel(model: IModel)fun handleText(text: String?)fun clearData()fun dataHandled(data: String?)fun dataCleared()}// 模型接口,定義了數據操作interface IModel {fun setViewModel(viewModel: IViewModel)fun handleData(data: String?)fun clearData()}MVVM 中的接口只定義了 ViewModel 和 Model,沒有 View 接口,是因為 View 是通過 databind 和 ViewModel 的。
我們再看看具體實現:
- Model 實現Model 的實現和上面基本一致,就是對數據的處理,處理完成后通知 ViewModel。
- ViewModel 實現
ViewModel 的實現要有些不同,我們采用 databind 進行 ViewModel 和 View 的綁定。
其中會定義兩個變量,inputText 是和 EditText 雙向綁定的數據,handledText 是和 TextView 雙向綁定的數據。
當 EditText 中輸入的數據有變化,會通知到 inputText 注冊的監聽器中,而 handledText 值的改變,會自動顯示到界面上。
class ViewModel : IViewModel {private var model: IModel? = null// View 綁定的數據,inputText 和 handledText 更新后會自動通知 View 更新界面var inputText: MutableLiveData<String> = MutableLiveData()var handledText: MutableLiveData<String> = MutableLiveData()init {// 注冊數據監聽,數據改變后通知 Model 去處理數據inputText.observeForever {handleText(it)}handledText.value = "default msg"}override fun handleText(text: String?) {if (TextUtils.isEmpty(text)) {handledText.value = "default msg"return}handledText.value = "handle data ..."model?.handleData(text)}// 清空按鈕的點擊事件綁定override fun clearData() {model?.clearData()}override fun setModel(model: IModel) {this.model = modelmodel.setViewModel(this)}// Model 數據處理完成,設置 handledText 的值,自動更新到界面override fun dataHandled(data: String?) {handledText.value = data}// Model 數據處理完成,設置 inputText 的值,自動更新到界面override fun dataCleared() {inputText.value = ""}}- View 實現
看一下 View 中的數據綁定。
通過上面的實現,當 EditText 中文字變化后,會自動修改 inputText 的值,觸發 inputText 監聽器,此時 ViewModel 將消息傳遞給 Model 進行處理,Model 數據處理完成后,通知 ViewModel 更新 handledText 的值,自動更新到界面上。
點擊清空按鈕時,自動調用綁定的點擊函數,通知 ViewModel 清空事件,ViewModel 將消息傳遞給 Model 進行數據清空,Model 數據處理完成后,通知 ViewModel 進行界面更新。
7.3 MVVM 架構模式的優缺點
優點:
- 結構清晰,職責劃分清晰
- 模塊間充分解耦
- 在 MVP 的基礎上,MVVM 把 View 和 ViewModel 也進行了解耦
缺點:
- Debug 困難,由于 View 和 ViewModel 解耦,導致 Debug 時難以一眼看出 View 的事件傳遞
- 代碼復雜性增大
八、架構模式的運用
上面的文章中,我們介紹了 MVC,MVP,MVVM 三種架構模式,以及其簡單的實現。這里我們再回過頭思考一下,什么時候該使用架構模式呢?
架構模式可以使代碼模塊清晰,職責分工明確,容易擴展,帶來的副作用就是會引入大量的接口,導致代碼文件數量激增。
我們在最開始說過,架構模式是用來解決特定的問題的,如果特定的問題在目前階段不是問題,或者不是主要問題,那么我們可以先不考慮使用架構模式。比如一個功能非常簡單,代碼量少,而后續又沒有擴展的需求,那我們直接使用傳統方式進行開發,快速且清晰,完全沒有必要為了架構而架構。
對于在開始沒有考慮架構模式的代碼,后續慢慢去重構,也是一個好的選擇。
總結來說就是:架構雖好,可不要貪杯哦~
總結
關于我:
更多面試內容,面試專題,flutter視頻 全套,音視頻從0到高手開發。關注GitHub:https://github.com/xiangjiana/Android-MS免費獲取面試PDF合集免費提供簡歷修改建議,獲取大廠面試PDF
https://u.wechat.com/ENywrVttLJsE4P0PTa0cnFs (二維碼自動識別)
總結
以上是生活随笔為你收集整理的mvvm模式和mvc的区别_Android 开发中的架构模式 -- MVC / MVP / MVVM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端图片有时候能显示有时候不显示_如何自
- 下一篇: 百度竞价排名点击软件_网络营销百问百答之