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

為什么要先介紹MVC?

如果你要想更佳深刻的理解MVP,并在實(shí)際開(kāi)發(fā)中靈活的應(yīng)用,那么就要先了解它的低配版MVC,他倆只是一步之遙,先了解MVC再學(xué)習(xí)MVP,MVP的優(yōu)勢(shì)才能凸顯出來(lái),這樣連貫性的學(xué)習(xí)才會(huì)加深對(duì)MVP的理解。

目錄

MVP那些事兒(1)……用場(chǎng)景說(shuō)話

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

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

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

MVP那些事兒(5)……中介者模式與MVP的關(guān)系【知識(shí)點(diǎn)】

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

MVP那些事兒(7)……Kotlin實(shí)現(xiàn)MVP【知識(shí)點(diǎn)】

MVP那些事兒(8)……當(dāng)MVP遇到Lifecycle【知識(shí)點(diǎn)】

MVP那些事兒(9)……探究MVP的最佳實(shí)踐

MVP那些事兒(10)……MVVM雙向綁定

MVP那些事兒(11)……基于MVVM的Architecture Components

快速回顧

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

在上一篇中,我們學(xué)習(xí)了MVC架構(gòu)圖原理和它的進(jìn)化過(guò)程,并通過(guò)is a,has a,依賴注入原則對(duì)MVC中三個(gè)對(duì)象進(jìn)行組合,同時(shí)從無(wú)到有的搭建出MVC框架的基本雛形,

靈與骨,血與肉

在上一篇中,我們的MVC框架已經(jīng)完成了初步的搭建,當(dāng)然,還不是框架最終形態(tài),雖然三個(gè)對(duì)象通過(guò)某種聯(lián)系組合了起來(lái),但讓框架真正運(yùn)轉(zhuǎn)起來(lái)還需要最關(guān)鍵的一個(gè)機(jī)制,那就是溝通機(jī)制,就好比人類(lèi),光有骨架和血肉還不能稱(chēng)之為一個(gè)完整的“人”,你還需要神經(jīng)系統(tǒng)幫助你去看,聽(tīng),和感受。

溝通機(jī)制

在Java的面向?qū)ο笤O(shè)計(jì)中,監(jiān)聽(tīng)是一種常用的溝通機(jī)制,在觀察者模式里,一個(gè)監(jiān)聽(tīng)機(jī)制所涉及到的對(duì)象包括:監(jiān)聽(tīng)者(Observer)、被監(jiān)聽(tīng)者(Obserable);涉及到的環(huán)節(jié)包括:訂閱(Subscribe)、發(fā)送事件、及處理事件。

場(chǎng)景

使用以下兩個(gè)需求作為本章場(chǎng)景:

1、列表展示

2、列表支持下拉刷新,上拉加載更多

實(shí)現(xiàn)監(jiān)聽(tīng)機(jī)制

既然監(jiān)聽(tīng)是一個(gè)常用的溝通手段,我們就開(kāi)始“升級(jí)”我們的框架

監(jiān)聽(tīng)的好處

在開(kāi)始之前,依舊要用一個(gè)場(chǎng)景來(lái)描述一下監(jiān)聽(tīng)的好處,還記得之前租房子的故事嗎?在這個(gè)故事里,我故意忽略了溝通的機(jī)制,就是為了留在這一章節(jié)講的,當(dāng)租客聯(lián)系到中介時(shí),這是一個(gè)主動(dòng)的動(dòng)作,租客是發(fā)起方,當(dāng)和中介建立聯(lián)系后,他們雙方互留電話,同時(shí)中介找到合適的房東,并且也留下了聯(lián)系方式,這個(gè)時(shí)候中介開(kāi)始等待房東的回應(yīng),這期間中介什么都干不了,一分鐘一個(gè)電話的詢問(wèn)房東是否考慮好了,那么中介的下場(chǎng)只有兩個(gè),房東很生氣,一分鐘一個(gè)電話,你沒(méi)事兒,我還有事兒呢,你等我消息不行嗎?直接拉黑?;蛘哂捎谥薪橐淮沃荒芴幚硪粋€(gè)事情,這件事處理不完,就不能處理下一件事,效率低下被公司開(kāi)除。租客也是一樣,一次次的去詢問(wèn)中介,找到房子了嗎?等待他的下場(chǎng)也有兩個(gè),一、一次次的電話,導(dǎo)致電話費(fèi)報(bào)表,二、由于電話費(fèi)太貴,打算一天問(wèn)一次,由于獲取消息不及時(shí),結(jié)果房子被別人租走了,也就是消息的即時(shí)性低,而露宿街頭(雖朱門(mén)酒肉臭,但別路有凍死骨,愿在外漂泊的你們?cè)谶@寒冷的冬天里有一個(gè)溫暖的所在)。

為了避免上面的悲劇發(fā)生,中介公司改善了溝通機(jī)制,首先從租戶的角度,通過(guò)主動(dòng)向租客匯報(bào)進(jìn)度來(lái)解決消息即時(shí)性的問(wèn)題,讓租戶第一時(shí)間得到最新情況,其次,中介不再催促房東,而是讓房東考慮好后通知中介,當(dāng)中介收到房東的消息后第一時(shí)間通知給租戶,通過(guò)這兩個(gè)環(huán)節(jié)的改造,一條高效的通知鏈就形成了。

為MVC框架增加監(jiān)聽(tīng)

Modle的職責(zé)是對(duì)數(shù)據(jù)的生產(chǎn)和處理,并在結(jié)束一些耗時(shí)的操作后,應(yīng)該主動(dòng)的通知給Controller,所以Model為被觀察對(duì)象,而Controller為觀察對(duì)象,它觀察著Model的一舉一動(dòng),為了能更好的觀察Model的行為,Controller派了一個(gè)“眼線”到Model中,這個(gè)“眼線”的職責(zé)就是監(jiān)聽(tīng)Model的一舉一動(dòng)。

第一步,定義一個(gè)“眼線”

/**我是一個(gè)“眼線”
public interface Observer {}

這里的眼線就是一個(gè)觀察對(duì)象的接口,但具體讓它做什么,我們還不清楚,通過(guò)接口的形式未來(lái)會(huì)有很好的擴(kuò)展性,定義完眼線,如何使用呢?

還記得上一篇中View是被怎么使用的嗎?它被Actvity實(shí)現(xiàn)了,也即是說(shuō)我們這里的“眼線”也應(yīng)該被某個(gè)對(duì)象去實(shí)現(xiàn),否則它將沒(méi)有任何用處,由于是Controller派出了一個(gè)“眼線”,所以應(yīng)該由Controller去使用,使用的兩種途徑,要么自己具備“眼線”的功能,也就是is a,要么就是自己招募一個(gè)“眼線”,Has a。

1、我就是眼線,眼線就是我

/**我是一個(gè)Contorller,同時(shí)我就是個(gè)眼線**/
public class TasksController implements Observer{
    void loadNomData() {}
}

TasksController,通過(guò)實(shí)現(xiàn)Observer接口,具備了觀察者的能力。

2、我招了個(gè)眼線

/**我是一個(gè)Contorller**/
public class TasksController{
    //我招募了一名眼線
    private Observer observer = new Observer() {};
    void loadNomData() {}
}

TasksController,通過(guò)內(nèi)部實(shí)例化了一個(gè)Observer接口,間接的獲得了觀察者的能力。

以上兩種都可以獲得觀察者的能力,但是從擴(kuò)展性來(lái)講,還是盡量去選擇第一種方式。

第二步,放置眼線

有了眼線后,我們還要將它放置在被觀察者的內(nèi)部,這才算完成了觀察者與被觀察者之間的訂閱。

public class MainActivity 
extends AppCompatActivity 
implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通過(guò)構(gòu)造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.setController(controller);
    }
}

這是上一篇內(nèi)容中的代碼段,未來(lái)都會(huì)圍繞著這段代碼進(jìn)行改進(jìn),看最下面這一行:

model.setController(controller);

其實(shí),這一步就是model持有了controller,由于我們現(xiàn)在的controller具備了觀察者的職責(zé),同時(shí)在我們真正的使用中沒(méi)有必要把整個(gè)controller的職責(zé)都暴露給model,而model也只需要controller觀察者的能力,好讓它即時(shí)的把結(jié)果告知controller,所以我們可以這樣改造一下這段代碼為:

model.addObserver(observer: controller);

看起來(lái)傳的參數(shù)依舊是controller,只不過(guò)改了一個(gè)方法名,這沒(méi)什么區(qū)別啊,我想說(shuō)的是區(qū)別還是有的,方法名的改變意味著這段代碼的業(yè)務(wù)變了,雖然都是controller,沒(méi)改之前是全部的controller,而下面的代碼是告訴大家,我只使用controller觀察者的部分,其他的我不關(guān)心,雖然你全給了我,但用那些是我的事情。

改造過(guò)后的Activity:

public class MainActivity
extends AppCompatActivity
implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通過(guò)構(gòu)造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
    }
}

第三步,發(fā)送事件

這個(gè)時(shí)候,Model已經(jīng)獲取到了觀察者,也就是Controller,那么當(dāng)Model自己發(fā)生變化時(shí),就可以即時(shí)的通知給Controller了,我們?cè)囍l(fā)一個(gè)事件,但是在發(fā)送事件前,不要忘了眼線還沒(méi)有具體的能力,我們只是定義了一個(gè)接口,眼線具體有什么能力還是要結(jié)合具體業(yè)務(wù)去定義,這不屬于架構(gòu)的部分,更偏向于業(yè)務(wù)層,這里我們就模擬當(dāng)Model獲取到數(shù)據(jù)后,通知Controller,我拿到數(shù)據(jù)了,所以讓眼線有通知數(shù)據(jù)ok的功能:

/**我是一個(gè)“眼線”
public interface Observer {
    //數(shù)據(jù)OK
    void onDataComplate(Data data);
}

目前眼線已經(jīng)準(zhǔn)備完畢,就等著Model來(lái)使用了,我們用Model來(lái)發(fā)送一個(gè)事件

Model :TasksRepository

    /**我是一個(gè)Model**/
    public class TasksRepository {
        //眼線集中營(yíng)
        public static ArrayList<Observer> observers = 
            new ArrayList<Observer>();
        viod addObserver(Observer observer){
            observers.add(observer);
        }
        //從服務(wù)器請(qǐng)求獲取數(shù)據(jù)
        void getTasks() {
            //訪問(wèn)服務(wù)器,耗時(shí)。。。服務(wù)器返回時(shí),
            Data data = fromServer();
            //發(fā)送事件
            for(Observer observer : observers){
                observer.onDataComplate(data);
            }
        }
        //從內(nèi)存緩存獲取數(shù)據(jù)
        Data getTaskCache() {}
        //從磁盤(pán)緩存獲取數(shù)據(jù)
        Data getTaskDiskCache(){}
        //保存一條數(shù)據(jù)
        boolean saveTask(Task task) {}
        //對(duì)數(shù)據(jù)進(jìn)行排序
        Data orderData(Data data, int orderType){}
    }

在實(shí)際的開(kāi)發(fā)中,Model可不是只為了某一個(gè)Controller去監(jiān)聽(tīng)的,它可以被任何想要監(jiān)聽(tīng)它的人監(jiān)聽(tīng),你只要送一個(gè)眼線過(guò)來(lái),當(dāng)Modle有變動(dòng)時(shí),Model會(huì)通知所有關(guān)心它的人,所以Model里面有一個(gè)Observer的集合:

public ArrayList<Observer> observers = 
            new ArrayList<Observer>();

當(dāng)Model發(fā)生了變化,就會(huì)遍歷這個(gè)集合去通知所有的觀察者,而眼線在這里派上了用場(chǎng)

for(Observer observer : observers){
    observer.onDataComplate(data);
}

第四步,接收事件

處理事件的特性是觀察者的本質(zhì),Controller既然是觀察者,那么處理事件應(yīng)該由自己去完成:

Controller :TasksController

/**我是一個(gè)Contorller**/
public class TasksController implements Observer{
    //接收事件
    void onDataComplate(Data data) {
        //處理事件
    }
    void loadNomData() {}
}

TasksController實(shí)現(xiàn)了Observer的onDataComplate方法,當(dāng)Model發(fā)送事件后,onDataComplate方法便能接收到,我們就可以在這里處理事件了,到此為止整個(gè)事件從創(chuàng)建到處理就完成了,這也就是觀察者模式的核心,如果以后需要自己實(shí)現(xiàn)一個(gè)觀察者模式,那么就按照上面四個(gè)步驟來(lái)寫(xiě),絕對(duì)不會(huì)懵圈而且思路會(huì)異常的清晰。

那么View呢?

上面的場(chǎng)景提到過(guò),當(dāng)房東考慮好后通知給中介,中介會(huì)第一時(shí)間通知租客結(jié)果,那么具體改如何做呢?

public class MainActivity
extends AppCompatActivity
implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通過(guò)構(gòu)造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
    }
}

回過(guò)頭來(lái)看Acivity的代碼中的這一段:

//初始化Controller,this就是View,通過(guò)構(gòu)造器注入
TasksController controller = 
            new TasksController(tasksView:this);

首先,通過(guò)構(gòu)造函數(shù),讓controller持有view,當(dāng)controller接收到model的通知時(shí),緊接著通知view,所以TasksController的代碼還需改進(jìn):

/**我是一個(gè)Contorller**/
public class TasksController implements Observer{
    //通過(guò)構(gòu)造函數(shù)接收view
    public TasksController(TasksView view) {
        this.view = view;
    }
    //接收事件
    void onDataComplate(Data data) {
        //處理事件,緊接著向view發(fā)送事件
        view.onDataBack(data);
    }
    void loadNomData() {}
}

我們看處理事件的部分,直接執(zhí)行了view的方法,也就是所謂的即刻通知。

讓View也接口化

按早之前Controller觀察者化的思路,我們能不能讓view也變成觀察者,當(dāng)然可以而且是必須的,讓view 去觀察Controller的變化,Controller又去觀察Model的變化,那么整個(gè)鏈?zhǔn)椒磻?yīng)就完成了。具體步驟就不分析了,上一個(gè)完整的代碼:

View :TasksView

    /**我是一個(gè)View,我本身就是個(gè)眼線**/
    public interface TaskView {
        void onDataBack(Data);
        //當(dāng)列表初始化后,告訴控制器該加載數(shù)據(jù)了
        void viewCreate();
        //更新列表
        void upDateList();
        //just for ui
        void beginLoadData();
    }

Activity:

public class MainActivity
extends AppCompatActivity
implments TasksView{
    private TasksController controller;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通過(guò)構(gòu)造器注入
        controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
        viewCreate();
    }
    //接收controller的事件,并處理
    void onDataBack(Data){
        //處理事件。。。
    }
    //當(dāng)列表初始化后,告訴控制器該加載數(shù)據(jù)了
    void viewCreate(){
        controller.loadNomData();
    }
    //更新列表
    void upDateList(){}
    //just for ui
    void beginLoadData(){}
}

總結(jié):

這一篇中,我們通過(guò)觀察者模式對(duì)我們的框架進(jìn)行了改進(jìn),通過(guò)監(jiān)聽(tīng),讓MVC的三個(gè)對(duì)象形成了一個(gè)事件傳送帶,事件就好比有了方向一般從Model出發(fā),經(jīng)過(guò)Controller最終流向View,而后期我們可以在這條鏈路上對(duì)我們的事件做任何想要做的操作,而最終的接收者View是完全不用關(guān)心的,亦或者view可以自定義自己想要的數(shù)據(jù),在Model還沒(méi)有發(fā)送事件前。說(shuō)的更確切點(diǎn),我們可以在事件的發(fā)送前,傳輸中,接收前,這三個(gè)點(diǎn)做很多我們希望做的事情,比如數(shù)據(jù)在接收前的一些排序的轉(zhuǎn)變,這些我們都會(huì)以接口的 方式暴露出來(lái)。到此,MVC的介紹結(jié)束,但框架的搭建還沒(méi)有完成,在接下來(lái)的被容里,我們通過(guò)MVP的方式對(duì)框架進(jìn)行進(jìn)一步的改進(jìn),同時(shí)加入一些實(shí)質(zhì)些的工具,讓框架具備一些基本的業(yè)務(wù)功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380