MVP那些事兒 (2) 初探MVC架構

不能用真名發表於2017-11-25

目錄

MVP那些事兒(1)……用場景說話

MVP那些事兒(2)……MVC架構初探

MVP那些事兒(3)……在Android中使用MVC(上)

MVP那些事兒(4)……在Android中使用MVC(下)

MVP那些事兒(5)……中介者模式與MVP的關係

MVP那些事兒(6)……MVC變身為MVP

MVP那些事兒(7)……Repository設計分析

為什麼要先介紹MVC?

如果你要想更佳深刻的理解MVP,並在實際開發中靈活的應用,那麼就要先了解它的低配版MVC,他倆只是一步之遙,先了解MVC再學習MVP,MVP的優勢才能凸顯出來,這樣連貫性的學習才會加深對MVP的理解。

我們在上一章提到,MVP那些事兒(2) 用場景說話 對我們的案例進行MVC的設計,也就是第一步,如何實現一個MVC,在實現之前我們先搞清楚幾點概念。

MVC的作用?它是設計模式嗎?是框架嗎?是架構嗎?

MVC屬於分層架構中的一員,無論是哪一個層從複用性和擴充套件性都是由當前業務所限制的,如果一開始就從複用和擴充套件上作為出發點來使用MVC的話,一定要有足夠的開發經驗(並不是不可以,分層架構也是可以有擴充套件性的,但要考慮主次),除非你非常明確未來軟體的複雜度。舉個例子,比如有一個演算法,它處理一組資料所需的時間關係如下:10條,需要1秒,20條需要1.8秒,30條需要2.5秒,,,,100條需要6秒,這個時間複雜度可以看出什麼,可以看出這個演算法處理的資料量越大,它的處理所消耗的時間遞增越緩慢,也就越是大資料量越能體現它的價值,再舉一個實際中的例子,人均100的餐廳,兩個人去也許每個人只能嚐到2~3種菜樣,但四個人去,每個人會嚐到4~6菜樣,20個人去,你也許可以把他們家的菜嚐個遍,MVC也是如此,軟體的複雜度越高,越能體現MVC的擴充套件價值,這就是我為什麼要講不要一上來就想從擴充套件性作為使用MVC的出發點,除非你是一個很有經驗的人,能提前估計出專案的一個大致的複雜度。更穩妥的方式應該是在專案的推進中不斷的優化自己的架構,很多萌新會進入到誤區,一看MVP大家都在用,自己設計一個吧,到最後發現根本就你一個人在吃菜,更關鍵的是別人想和你吃也吃不了,不通用,根本無法體現它的擴充套件價值,負責人發現後第一反應是,你搞這麼多介面抽象類幹什麼?就你一個人用,搞事情。

MVP那些事兒 (2)     初探MVC架構

MVC不是簡單的設計模式

一個只為了分層就可以被叫做設計模式的話,那麼外觀模式看來還不是最尷尬的模式,所以它不能簡單的稱呼為設計模式,因為它的範圍和深度更加立體,稱為架構更為妥當,當然也可以稱之為複合設計模式,它裡面包含了,策略,組合,觀察者,等設計模式。那麼,架構和框架的關係又是什麼呢?

架構和框架說的不是一回事兒

MVC是一種架構思想,而基於這個思想的框架也叫MVC框架,在ios的開發中,系統為我們實現了公共的檢視類UIView,和控制器類UIViewController,還有大名鼎鼎的Struts2框架,都是基於MVC架構設計出來的框架(基於MVVM架構的DataBinding,Architecture Components框架),框架是一個有邊界的可擴充套件集,它在可擴充套件的同時也是有邊界的,也就是說你只能在這個框架裡玩耍,而無法超出這個框框。如果你只是在專案中使用了MVC的思想去做分層,那麼這個專案或模組用的就是MVC架構設計,如果你用了Struts框架,那麼這個專案使用了MVC框架。當你向別人介紹你的專案時,你可以這麼說:我的專案使用的是MVVM架構,具體用到了Architecture Components作為框架來進行開發,千萬不要說成:大家好,我來介紹一款基於MVVM框架設計的Architecture Components架構,這聽的得多彆扭啊。

一句話總結——架構是藍圖,而框架是實實在在的產品,MVP架構就一個,而基於MVP架構設計的框架可以是千千萬萬個。

以上就是對MVC的一個大致的介紹,學習MVC之前,要先會寫,學會了寫,再學習用,接下來我們先實現一個MVC框架。

實現MVC什麼?MVC(框架)

在第一章的場景裡我們挑一個需求,我們來實現一個向服務端請求資料,並顯示在列表的需求。

需求時序圖
在Android中我們實現上面的需求,通常情況是在介面的Create時,呼叫一個請求介面,通過非同步的方式返回資料,待資料返回時更新UI,我們用一個Activity來實現:

public class TasksActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        //...初始化
        //請求列表資料
        Data data = loadData();
    }
    void onDataCallBack (Data data) {
        //更新列表
        upDateList(data);
    }
}
複製程式碼

這是一段簡單的示例程式碼,通常情況下我們用像loadData這樣的方法去請求資料,並且繫結監聽,監聽資料的返回,當資料返回時,像onDataCallBack這樣的回撥方法會被觸發,同時進行ui的更新。

同樣的需求,用MVC寫一遍

MVC分別為Model,View,Controller,顧名思義,首先我們需要建立這三個物件,第二步,我們把這三個物件組合起來,我們先一個一個的認識它們,逐個擊破,首先從簡單的來,Model,

什麼是Model,它的職責

邏輯層(領域層)Domain Object,邏輯執行體,除此之外也可以兼顧JavaBean的職責,但Model層可不是簡單的Value Object。而 JavaBean也就是Value Object,只是用來封裝資料,不具備Model層的職責,當然也可以把JavaBean當作Model類的一個內部類來使用,當然也可以單獨一個類,通常我們也會這麼作,方便複用。 萌新⚠️

1、JavaBean不是Model,但Model也可以包含JavaBean的職責,但不是必須的。 2、Model是用來處理資料的,如獲取資料(本地或者服務端),資料處理,如CURD。

按照上面的定義我們來寫一個Model

/**我是一個Model**/
public class TasksRepository {
    //從伺服器請求獲取資料
    void getTasks() {}
    //從記憶體快取獲取資料
    Data getTaskCache() {}
    //從磁碟快取獲取資料
    Data getTaskDiskCache(){}
    //儲存一條資料
    boolean saveTask(Task task) {}
    //對資料進行排序
    Data orderData(Data data, int orderType){}
}
複製程式碼

首先我們建立一個Model類名為TasksRepository,首先不要關心裡面的Data和Task物件,它們只是普通的Bean物件,TasksRepository裡面包含了五個方法,按照之前的定義,它是有獲取資料的職責的,所以這其中三個方法都是和獲取資料相關,比如getTasks,你可以呼叫網路元件進行資料的獲取,它還可以對資料進行CURD的操作,比如savaTask的方法用來儲存一條資料,還可以對資料進行業務處理,比如用orderData方法對資料進行重新的排序。

一句話總結,Model負責獲取資料,運算元據,和對資料進行業務處理。

什麼是View,它的職責

接下來我們講一講View,View就是我們的檢視層。

1、它的主要職責為呈現Model的資料、主動詢問狀態或被動的監聽 2、通知控制器Controller去處理一些事情 3、接收Controller,編輯自己與Model無關的狀態

按照View的職責我們來設計一下這個類

/**我是一個View**/
public class TaskView {
    //當列表初始化後,告訴控制器該載入資料了
    void viewCreate() {
        controller.loadNomData();
    }
    //更新列表
    void upDateList() {
        //主動請求Model獲取資料
        Data data = tasksRepository.getTaskCache();
        //更新ui
        list.update(data);
    }
    
    void beginLoadData(){
        list.showHead();
    }
}
複製程式碼

在TaskView中也就是我們的檢視物件,它包含了倆個物件,分別為controller和taskeRepository,controller就是控制器物件,我們下一個講它,taskeRepository就是我們的模型,上面提到過它,先不要關心這兩個物件是怎麼初始化來的,要關心的是View檢視物件是包含了C和M的,按照職責它必須這麼做,首先像viewCreate這類方法一般是在介面初始化時呼叫的(在Android 中可能是Activity或者Fragment初始化時呼叫的,也可能是某一個執行動作),讓controller去執行loadNomData的方法請求資料,這裡唯一需要注意的是,並沒有明確的告訴controller請求的資料是從什麼地方來的,也許是快取,也許是網路請求,view是無需關心這一點的。同時還要注意的是,在講解Model的時候已經明確了Model是有獲取資料的職責,但是在上面的示例中,**為什麼是controller去負責獲取資料,而不是用Model?也就是我們的tasksRepository物件?**請大家記住這個疑問,會在講解Controller時回答。

第二個方法upDataList方法,

//更新列表
void upDateList() {
    //主動請求Model獲取資料
    Data data = tasksRepository.getTaskCache();
   //更新ui
    list.update(data);
}
複製程式碼

它的內部是通過tasksRepository物件的getTaskCache()方法獲取資料,這個方法在Model的定義裡面,大家還有印象吧。講到這裡,**大家可能發現有一個嚴重的漏洞,view在執行tasksRepository.getTaskCache()時,是怎麼知道這個資料已經準備好了呢?**看view的職責1:

主動的問詢或者監聽Model

我們把呼叫tasksRepository.getTaskCache()看作為主動的問詢,而在主動問詢前,View應該得到一個有效的通知,這個通知應該由Model發起,當監聽到Model:我的資料準備好了,你來拿吧時,View會去主動的向Model獲取資料。舉個現實中的例子,你在網上買東西,現在一般都往快遞櫃裡投放,等到簡訊通知你快遞到了時,你才會去快遞櫃裡拿商品,但也許你實在等不及了,也可以天天打電話,我們就不詳細討論了,View和Model但關係也就是觀察者與被觀察者的關係。可是,在上面的示例中我們沒有寫出監聽Model的程式碼,請大家記住這個疑問,我會在後面講解三者的依賴繫結時會揭曉答案。 最後一個方法,beginLoadData(),

void beginLoadData(){
        list.showHead();
}
複製程式碼

當開始請求資料時,list會顯示自己的頭佈局,像beginLoadData這樣編輯view本身的方法在實際開發過程中還有很多,它們關注的是控制元件本身的狀態,最重要的,這樣的方法可能會被Controller隨時的呼叫,我們在講解Controller時進行講解。

什麼是Controller,它的職責

Controller,也就是我們的控制器,它把檢視的操作傳送到模型。

接收View的操作,並轉調給Model 改變View的狀態

按照上面的職責,我們嘗試的設計一下這個類:

/**我是一個Contorller**/
public class TasksController {
    
    void loadNomData() {
        if(tasksRepository.getTaskCache() == null){
            //執行Modle
            tasksRepository.getTasks();
            //執行View
            view.beginLoadData();
        }
    }
}
複製程式碼

在對Controller講解之前,我們先停頓一下,在之前的介紹中我們好像遺留了幾個問題,在介紹View時,我用加租文字的方式標示了三個問題,在介紹Controller時我們將要解決掉問題1,和問題3。我們一起回顧一下這2個問題:

疑問1,在講解Model的時候已經明確了Model是有獲取資料的職責,但是在上面的示例中,為什麼是controller.loadNomData()去負責獲取資料,而不是用Model?也就是我們的tasksRepository物件?

這個問題要從Contrller的職責說起:接收view的操作,並轉調給Modle。什麼是view的操作,比如list的下拉重新整理操作,轉調給Model,也就是說其實Controller並沒有實際的實現載入資料的程式碼,而是讓Model去執行,解釋完以後,看一下TasksController裡的第一個方法loadNomData(),

void loadNomData() {
        if(tasksRepository.getTaskCache() == null){
            //執行Modle
            tasksRepository.getTasks();
            //執行View
            view.beginLoadData();
        }
    }
複製程式碼

它內部第一條語句:

tasksRepository.getTasks();
複製程式碼

而tasksRepositor物件就是我們的Model,它執行了getTasks()的操作,其實controller的loadNomData()方法只是對這個過程進行了一個封裝。聽完上面的解釋,我們知道Controller是這麼設計的,可是又一個問題出現了,雖然我們知道了Controller轉調了Model的方法,可是我為什麼要這麼做?我直接在View裡呼叫tasksRepository.getTasks();不可以嗎? 清大家記住這個問題,我下一章為大家解答,我們先學會寫,再去研究怎麼用。

疑問2,像beginLoadData這樣編輯View本身狀態的方法在實際開發過程中還有很多,它們純粹關注的是控制元件本身的狀態,最重要的,這樣的方法可能會被Controller隨時的呼叫。

我們先回顧一下這個方法beginLoadData()

//controller會隨時呼叫
void beginLoadData(){
        //顯示列表的頭部,改變了列表的屬性
        list.showHead();
}
複製程式碼

我們還是從Controller的職責來說,Controller有權利對View的狀態進行改變,不管是通知的形式,還是直接的訪問。而且View的職責裡也說明了,它是可以接收Controller更改自己的狀態,而更關鍵的一點這個狀態的改變是無需依賴Model的,更加的純粹。

有了上面問題的承上啟下,我們再回頭看一下TasksController裡的loadNomData()方法,它首先判斷了是否可以從tasksRepository裡拿到快取,如果沒有就執行getTasks(),同時通知view我要開始載入資料了,view會在beginLoadData()方法中對自己的列表控制元件進行一個顯示頭部的處理,相當規範的一個封裝。

**總結:**到此,我們已經學習完了MVC這三個層的定義和職責。在文章的開頭我有講過,要想用MVP,就要先用MVC,要想用MVC就要先會"寫"MVC,寫才是第一步,我們首先把MVC這三個層,拆分成三個片段,每一個片段都規範好它們的職責,然後我們再想辦法把它在組裝在一起,在下一章我們要解決以下問題:

1、如何把這三個片段組裝起來?在Android裡怎麼寫? 2、view在執行tasksRepository.getTaskCache()時,是怎麼知道tasksRepository資料已經準備好了呢?怎麼通知view的?

MVP那些事兒(3)……在Android中使用MVC(上)

謝謝大家關注和評論,更新的有些慢,不如關注我一下,隨時知道更新進度。

相關文章