【這是 ZY 第 15 篇原創技術文章】php
關於架構的定義,其實在不少書籍和文章中都是不一樣的,很難作一個統一。這裏列舉兩個定義:
在維基百科裏是這樣定義的:
軟件架構是一個系統的草圖。軟件架構描述的對象是直接構成系統的抽象組件。各個組件之間的鏈接則明確和相對細緻地描述組件之間的通信。在實現階段,這些抽象組件被細化爲實際的組件,好比具體某個類或者對象。
在 IEEE 軟件工程標準詞彙中是這樣定義的:
架構是以組件、組件之間的關係、組件與環境之間的關係爲內容的某一系統的基本組織結構,以及指導上述內容設計與演化的原理。java
關於更多的定義,推薦閱讀《軟件架構設計:程序員向架構師轉型必備》第二章
...android
在看過茫茫多的架構定義之後,我理解的架構是這樣的:git
具體解釋一下,首先是要有特定的問題,沒有問題空談架構,彷彿是空中樓閣,沒有實用價值,而對應到不一樣的問題,會有不一樣的解決方式。
其次是模塊的劃分要根據特定的原則,沒有原則隨意劃分,也就無從去評估一個架構的好壞。最後就是模塊間的通訊機制,讓系統成爲一個總體。程序員
最後,架構模式,其實更多的是一種思想,一種規則,每每一種架構模式可能會有不一樣的實現方式,而實現方式之間,只有合適與否,並無對錯之分。github
上面咱們介紹了架構的定義,根據這個定義,咱們在後面分析架構模式的時候,也會從這三方面進行。設計模式
對於咱們 Android 開發者來講,常見的架構模式基本上就是 MVC,MVP,MVVM,這三種也是開發 GUI 應用程序常見的模式。
除此以外還有 分層模式,客戶端-服務器模式(CS模式),主從模式,管道過濾器模式,事件總線模式 等等。
這篇文章仍是具體分析 MVC,MVP,MVVM 這三種架構模式。服務器
咱們在瞭解架構的定義之後,可能會想,爲何要用這些架構模式呢?在咱們不瞭解這些模式以前,也是同樣的開發。相似設計模式,其實架構模式的目的不是爲了讓應用軟件開發出來,而是讓結構更清晰,分工更明確,擴展更方便等等。網絡
咱們能夠看看,在不使用架構模式以前咱們是怎麼開發的。
舉個簡單的栗子,咱們界面上有 EditText,TextView,Button 三個控件,要實現的功能也比較簡單:數據結構
界面以下:
咱們看看不使用架構模式是怎麼開發的,也就是咱們通常經常使用的開發方式:
<?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>
複製代碼
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 架構,在不一樣的框架裏,實現會有些差異,這也正說明了架構是一種思想。咱們這裏選用一種比較主流的實現。
咱們能夠看到,上面不使用架構進行開發,帶來的問題是 Activity / Fragment 邏輯臃腫,不利於擴展。因此 MVC 就要解決的問題就是:控制邏輯,數據處理邏輯和界面交互耦合。
這裏先插一個題外話,其實咱們做爲程序員,寫代碼不只要實現需求,還要讓代碼易讀,易擴展。這一點,每每也能體現功力,並非說使用了各類奇技淫巧纔是大神。
不知道你們是否有接觸過 Java Swing 桌面應用開發,在 Java Swing 中,界面 / 控件的設置,也是用 Java 代碼來實現的,若是不採用架構,最後的結果就是控制邏輯,數據處理以及頁面展現的代碼都集中在一個類中,讀者朋友們能夠想象一下,這樣的代碼簡直是難以維護。
爲了解決上面的問題,MVC 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-控制器(Controller)。各個部分的功能以下:
咱們再看看三者之間是怎麼通訊的。
在介紹通訊以前,咱們先解釋一下通訊中的數據是什麼。其實在 Android 開發中,通訊數據能夠理解爲兩種,一種是數據結構,也就是網絡請求,本地存儲等通訊使用的 JavaBean,另外一種是事件,也就是控件產生的動做,包括觸摸,點擊,滑動等等。咱們在通訊過程當中,也主要關注這兩種數據。
在 MVC 架構中,View 產生事件,通知到 Controller,Controller 中進行一系列邏輯處理,以後通知給 Model 去更新數據,Model 更新數據後,再將數據結構通知給 View 去更新界面。
這就是一個完整 MVC 的數據流向。
理解了 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 中對數據的處理是添加了 "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 的實現比較簡單,將操做直接轉發給 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) {
}
}
複製代碼
這裏 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 結構。
優勢:
缺點:
MVP 要解決的問題和 MVC 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,同時能將 MVC 中的 View 和 Model 解耦。
MVP 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-控制器(Presenter)。各個部分的功能以下:
咱們能夠看到,MVP 中的各個角色劃分,和 MVC 基本上類似,那麼區別在哪裏呢?區別就在角色的通訊上。
MVP 和 MVC 最大的不一樣,就是 View 和 Model 不相互持有,都經過 Presenter 作中轉。View 產生事件,通知給 Presenter,Presenter 中進行邏輯處理後,通知 Model 更新數據,Model 更新數據後,通知數據結構給 Presenter,Presenter 再通知 View 更新界面。
這就是一個完整 MVP 的數據流向。
理解了 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。
接着看下接口的實現:
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 去中轉。
這裏依舊是 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 的實現比較簡單,沒有太多的業務邏輯,實際應用中,這裏會進行業務邏輯的處理。
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()
}
}
複製代碼
優勢:
缺點:
MVVM 要解決的問題和 MVC,MVP 大同小異:控制邏輯,數據處理邏輯和界面交互耦合,而且同時能將 MVC 中的 View 和 Model 解耦,還能夠把 MVP 中 Presenter 和 View 也解耦。
MVVM 架構裏,將邏輯,數據,界面的處理劃分爲三個部分,模型(Model)-視圖(View)-邏輯(ViewModel)。各個部分的功能以下:
咱們能夠看到,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 的數據流向。
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 的實現和上面基本一致,就是對數據的處理,處理完成後通知 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 的實現要有些不一樣,咱們採用 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 中的數據綁定。
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 進行界面更新。
優勢:
缺點:
上面的文章中,咱們介紹了 MVC,MVP,MVVM 三種架構模式,以及其簡單的實現。這裏咱們再回過頭思考一下,何時該使用架構模式呢?
架構模式可使代碼模塊清晰,職責分工明確,容易擴展,帶來的反作用就是會引入大量的接口,致使代碼文件數量激增。
咱們在最開始說過,架構模式是用來解決特定的問題的,若是特定的問題在目前階段不是問題,或者不是主要問題,那麼咱們能夠先不考慮使用架構模式。好比一個功能很是簡單,代碼量少,然後續又沒有擴展的需求,那咱們直接使用傳統方式進行開發,快速且清晰,徹底沒有必要爲了架構而架構。
對於在開始沒有考慮架構模式的代碼,後續慢慢去重構,也是一個好的選擇。
總結來講就是:架構雖好,可不要貪杯哦~
www.infoq.cn/article/an-…
zh.wikipedia.org/wiki/軟件架構
www.jianshu.com/p/4ce4dcb43…
《軟件架構設計--程序員向架構師轉型必備》
《架構實戰》
《架構之美》