Android 開發中的架構模式 -- MVC / MVP / MVVM

這是 ZY 第 15 篇原創技術文章php

預備知識

  1. 瞭解 Android 基本開發

看完本文能夠達到什麼程度

  1. 瞭解如何分析一個架構模式
  2. 掌握 MVC,MVP,MVVM 架構定義和實現

閱讀前準備工做

  1. clone CommonTec 項目,其中 architecture 模塊是本文架構模式的對應的示例代碼

文章概覽

summary

1、什麼是架構

關於架構的定義,其實在不少書籍和文章中都是不一樣的,很難作一個統一。這裏列舉兩個定義:
在維基百科裏是這樣定義的:
軟件架構是一個系統的草圖。軟件架構描述的對象是直接構成系統的抽象組件。各個組件之間的鏈接則明確和相對細緻地描述組件之間的通信。在實現階段,這些抽象組件被細化爲實際的組件,好比具體某個類或者對象。
在 IEEE 軟件工程標準詞彙中是這樣定義的:
架構是以組件、組件之間的關係、組件與環境之間的關係爲內容的某一系統的基本組織結構,以及指導上述內容設計與演化的原理。java

關於更多的定義,推薦閱讀《軟件架構設計:程序員向架構師轉型必備》第二章
...android

在看過茫茫多的架構定義之後,我理解的架構是這樣的:git

  1. 爲了解決特定的問題而提出
  2. 按照特定的原則將系統總體進行模塊/組件/角色的劃分
  3. 創建模塊/組件/角色間的溝通機制

具體解釋一下,首先是要有特定的問題,沒有問題空談架構,彷彿是空中樓閣,沒有實用價值,而對應到不一樣的問題,會有不一樣的解決方式。
其次是模塊的劃分要根據特定的原則,沒有原則隨意劃分,也就無從去評估一個架構的好壞。最後就是模塊間的通訊機制,讓系統成爲一個總體。程序員

最後,架構模式,其實更多的是一種思想,一種規則,每每一種架構模式可能會有不一樣的實現方式,而實現方式之間,只有合適與否,並無對錯之分github

2、如何分析一種架構模式

上面咱們介紹了架構的定義,根據這個定義,咱們在後面分析架構模式的時候,也會從這三方面進行。設計模式

  1. 架構解決了什麼問題
    知道了架構模式要解決的問題,咱們才能針對性的去看,去想其解決方法是否得當,是否合適。
  2. 架構模式是如何劃分角色的
    架構中最重要的就是角色 / 模塊的劃分,理解了架構模式中的角色劃分,才能更好的理解其結構。
  3. 角色間是如何通訊的
    角色間的通訊也是重要的。相同的角色劃分,採用不一樣的通訊方式,每每就構成了不一樣的架構模式。
    角色間通訊咱們能夠理解爲數據的流向。在 Android 開發中,通訊中的數據能夠理解爲兩種,一種是數據結構,也就是網絡請求,本地存儲等通訊使用的 JavaBean,另外一種是事件,也就是控件產生的動做,包括觸摸,點擊,滑動等等。咱們在通訊過程當中,也主要關注這兩種數據。

3、常見的架構模式有哪些

對於咱們 Android 開發者來講,常見的架構模式基本上就是 MVC,MVP,MVVM,這三種也是開發 GUI 應用程序常見的模式。
除此以外還有 分層模式,客戶端-服務器模式(CS模式),主從模式,管道過濾器模式,事件總線模式 等等。
這篇文章仍是具體分析 MVC,MVP,MVVM 這三種架構模式。服務器

4、不使用架構以前 App 是怎麼開發的

咱們在瞭解架構的定義之後,可能會想,爲何要用這些架構模式呢?在咱們不瞭解這些模式以前,也是同樣的開發。相似設計模式,其實架構模式的目的不是爲了讓應用軟件開發出來,而是讓結構更清晰,分工更明確,擴展更方便等等。網絡

咱們能夠看看,在不使用架構模式以前咱們是怎麼開發的。
舉個簡單的栗子,咱們界面上有 EditText,TextView,Button 三個控件,要實現的功能也比較簡單:數據結構

  1. EditText 接受用戶輸入的內容
  2. 處理用戶輸入的數據
  3. 數據處理後輸出到 TextView 中
  4. 點擊 Button 清空用戶的輸入

界面以下:

pro

咱們看看不使用架構模式是怎麼開發的,也就是咱們通常經常使用的開發方式:

  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">

    <TextView android:id="@+id/titleText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Normal" />

    <EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="50dp" android:textColor="@android:color/darker_gray" />

    <TextView android: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" />

    <TextView android: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 中邏輯臃腫,後續擴展牽一髮而動全身。並且職責劃分不清晰,給後續維護也帶來了困難。
既然如此,咱們看看使用架構模式改造後是什麼樣子的。

5、MVC 架構

5.1 模式介紹

其實關於 MVC 架構,在不一樣的框架裏,實現會有些差異,這也正說明了架構是一種思想。咱們這裏選用一種比較主流的實現。

mvc

1. 解決什麼問題

咱們能夠看到,上面不使用架構進行開發,帶來的問題是 Activity / Fragment 邏輯臃腫,不利於擴展。因此 MVC 就要解決的問題就是:控制邏輯,數據處理邏輯和界面交互耦合
這裏先插一個題外話,其實咱們做爲程序員,寫代碼不只要實現需求,還要讓代碼易讀,易擴展。這一點,每每也能體現功力,並非說使用了各類奇技淫巧纔是大神。
不知道你們是否有接觸過 Java Swing 桌面應用開發,在 Java Swing 中,界面 / 控件的設置,也是用 Java 代碼來實現的,若是不採用架構,最後的結果就是控制邏輯,數據處理以及頁面展現的代碼都集中在一個類中,讀者朋友們能夠想象一下,這樣的代碼簡直是難以維護。

2. 如何劃分角色

爲了解決上面的問題,MVC 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-控制器(Controller)。各個部分的功能以下:

  • Model 模型,負責數據的加載和存儲。
  • View 視圖,負責界面的展現。
  • Controller 控制器,負責邏輯控制。
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? = null
    private 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? = null

    override 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 架構模式的優缺點

優勢:

  1. 結構清晰,職責劃分清晰
  2. 下降耦合
  3. 有利於組件重用

缺點:

  1. 其實咱們上述的示例,已是通過優化的 MVC 結構了,通常來講,Activity / Fragment 會承擔 View 和 Controller 兩個角色,就會致使 Activity / Fragment 中代碼較多
  2. Model 直接操做 View,View 的修改會致使 Controller 和 Model 都進行改動
  3. 增長了代碼結構的複雜性

6、MVP 架構

6.1 模式介紹

mvp

1. 解決什麼問題

MVP 要解決的問題和 MVC 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,同時能將 MVC 中的 View 和 Model 解耦

2. 如何劃分角色

MVP 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-控制器(Presenter)。各個部分的功能以下:

  • Model 模型,負責數據的加載和存儲。
  • View 視圖,負責界面的展現。
  • Presenter 控制器,負責邏輯控制。
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 處理完成數據通知 Presenter
    fun dataHandled(data: String)
    // Model 清除數據後通知 Presenter
    fun dataCleared()
    // View 中 EditText 文字變化後通知 Presenter
    fun onTextChanged(text: String)
    // View 中 Button 點擊事件通知 Presenter
    fun onClearBtnClicked()
}
複製代碼

上面定義了 View,Model,Presenter 三個接口,其中 View 和 Model 會持有 Presenter,Presenter 持有 View 和 Model。
接着看下接口的實現:

  • Model 的實現
class HandleModel : IModel {
    private var presenter: IPresenter? = null
    private var handler = Handler(Looper.getMainLooper())

    override fun handleData(data: String) {
        if (TextUtils.isEmpty(data)) {
            return
        }
        handler.removeCallbacksAndMessages(null)
        // 延遲來模擬網絡或者磁盤操做
        handler.postDelayed({
            // 數據處理完成,通知 Presenter
            presenter?.dataHandled("handled data: $data")
        }, 3000)
    }

    override fun clearData() {
        handler.removeCallbacksAndMessages(null)
        // 數據清理完成,通知 Presenter
        presenter?.dataCleared()
    }

    override fun setPresenter(presenter: IPresenter) {
        this.presenter = presenter
    }

}
複製代碼

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? = null

    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 = "MVP"

        edit.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                // 傳遞 文字修改 事件給 Presenter
                mpresenter?.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 {
            // 傳遞按鈕點擊事件給 Presenter
            mpresenter?.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? = null
    private var view: IView? = null

    override 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 架構模式的優缺點

優勢:

  1. 結構清晰,職責劃分清晰
  2. 模塊間充分解耦
  3. 有利於組件的重用

缺點:

  1. 會引入大量的接口,致使項目文件數量激增
  2. 增大代碼結構複雜性

7、MVVM 架構

7.1 模式介紹

mvvm

1. 解決什麼問題

MVVM 要解決的問題和 MVC,MVP 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,而且同時能將 MVC 中的 View 和 Model 解耦,還能夠把 MVP 中 Presenter 和 View 也解耦。

2. 如何劃分角色

MVVM 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-邏輯(ViewModel)。各個部分的功能以下:

  • Model 模型,負責數據的加載和存儲。
  • View 視圖,負責界面的展現。
  • ViewModel 控制器,負責邏輯控制。
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。

class HandleModel : IModel {
    private var viewModel: IViewModel? = null
    private var handler = Handler(Looper.getMainLooper())

    override fun handleData(data: String?) {
        if (TextUtils.isEmpty(data)) {
            return
        }
        handler.removeCallbacksAndMessages(null)
        // 延遲來模擬網絡或者磁盤操做
        handler.postDelayed({
            // 數據處理完成通知 ViewModel
            viewModel?.dataHandled("handled data: $data")
        }, 3000)
    }

    override fun clearData() {
        handler.removeCallbacksAndMessages(null)
        // 數據清理完成通知 ViewModel
        viewModel?.dataCleared()
    }

    override fun setViewModel(viewModel: IViewModel) {
        this.viewModel = 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 = model
        model.setViewModel(this)
    }

    // Model 數據處理完成,設置 handledText 的值,自動更新到界面
    override fun dataHandled(data: String?) {
        handledText.value = data
    }

    // Model 數據處理完成,設置 inputText 的值,自動更新到界面
    override fun dataCleared() {
        inputText.value = ""
    }
}
複製代碼
  • View 實現

看一下 View 中的數據綁定。

class MVVMFragment : Fragment() {
    companion object {
        fun newInstance(): Fragment {
            return MVVMFragment()
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // 使用 databind 進行數據綁定
        var binding: ArchitectureBindingBinding = DataBindingUtil.inflate(inflater, R.layout.architecture_binding, container, false)
        binding.lifecycleOwner = this
        val viewModel = ViewModel()
        viewModel.setModel(HandleModel())
        binding.viewmodel = viewModel
        return binding.root
    }
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <!--定義 View 中綁定的數據-->
    <data>
        <variable name="viewmodel" type="com.zy.architecture.mvvm.ViewModel" />
    </data>

    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp" tools:context=".MainActivity">

        <TextView android:id="@+id/titleText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="MVVM" />

        <!--雙向綁定 inputText 到 EditText-->
        <EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="50dp" android:text="@={viewmodel.inputText}" android:textColor="@android:color/darker_gray" />

        <!--綁定 handledText 到 TextView-->
        <TextView android:id="@+id/msgText" android:layout_width="wrap_content" android:layout_height="30dp" android:layout_marginTop="10dp" android:text="@{viewmodel.handledText}" android:textColor="@android:color/darker_gray" />

        <!--綁定清空數據的點擊事件 到 TextView-->
        <TextView android:id="@+id/clearText" android:layout_width="match_parent" android:layout_height="30dp" android:layout_marginTop="10dp" android:background="@color/colorPrimary" android:gravity="center" android:onClick="@{() -> viewmodel.clearData()}" android:text="clear" android:textColor="@android:color/white" />
    </LinearLayout>
</layout>
複製代碼

經過上面的實現,當 EditText 中文字變化後,會自動修改 inputText 的值,觸發 inputText 監聽器,此時 ViewModel 將消息傳遞給 Model 進行處理,Model 數據處理完成後,通知 ViewModel 更新 handledText 的值,自動更新到界面上。
點擊清空按鈕時,自動調用綁定的點擊函數,通知 ViewModel 清空事件,ViewModel 將消息傳遞給 Model 進行數據清空,Model 數據處理完成後,通知 ViewModel 進行界面更新。

7.3 MVVM 架構模式的優缺點

優勢:

  1. 結構清晰,職責劃分清晰
  2. 模塊間充分解耦
  3. 在 MVP 的基礎上,MVVM 把 View 和 ViewModel 也進行了解耦

缺點:

  1. Debug 困難,因爲 View 和 ViewModel 解耦,致使 Debug 時難以一眼看出 View 的事件傳遞
  2. 代碼複雜性增大

8、架構模式的運用

上面的文章中,咱們介紹了 MVC,MVP,MVVM 三種架構模式,以及其簡單的實現。這裏咱們再回過頭思考一下,何時該使用架構模式呢?
架構模式可使代碼模塊清晰,職責分工明確,容易擴展,帶來的反作用就是會引入大量的接口,致使代碼文件數量激增。
咱們在最開始說過,架構模式是用來解決特定的問題的,若是特定的問題在目前階段不是問題,或者不是主要問題,那麼咱們能夠先不考慮使用架構模式。好比一個功能很是簡單,代碼量少,然後續又沒有擴展的需求,那咱們直接使用傳統方式進行開發,快速且清晰,徹底沒有必要爲了架構而架構。
對於在開始沒有考慮架構模式的代碼,後續慢慢去重構,也是一個好的選擇。

總結來講就是:架構雖好,可不要貪杯哦~

總結

summary

參考資料

www.infoq.cn/article/an-…
zh.wikipedia.org/wiki/軟件架構
www.jianshu.com/p/4ce4dcb43…
《軟件架構設計--程序員向架構師轉型必備》
《架構實戰》
《架構之美》

aboutme
相關文章
相關標籤/搜索