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

不能用真名發表於2017-12-02

為什麼要先介紹MVC?

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

目錄

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

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

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

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

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

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

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

快速回顧

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

在上一篇中,我們學習了MVC架構圖原理和它的進化過程,並通過is a,has a,依賴注入原則對MVC中三個物件進行組合,同時從無到有的搭建出MVC框架的基本雛形,

靈與骨,血與肉

在上一篇中,我們的MVC框架已經完成了初步的搭建,當然,還不是框架最終形態,雖然三個物件通過某種聯絡組合了起來,但讓框架真正運轉起來還需要最關鍵的一個機制,那就是溝通機制,就好比人類,光有骨架和血肉還不能稱之為一個完整的“人”,你還需要神經系統幫助你去看,聽,和感受。

溝通機制

在Java的物件導向設計中,監聽是一種常用的溝通機制,在觀察者模式裡,一個監聽機制所涉及到的物件包括:監聽者(Observer)、被監聽者(Obserable);涉及到的環節包括:訂閱(Subscribe)、傳送事件、及處理事件。

場景

使用以下兩個需求作為本章場景:

1、列表展示

2、列表支援下拉重新整理,上拉載入更多

實現監聽機制

既然監聽是一個常用的溝通手段,我們就開始“升級”我們的框架

監聽的好處

在開始之前,依舊要用一個場景來描述一下監聽的好處,還記得之前租房子的故事嗎?在這個故事裡,我故意忽略了溝通的機制,就是為了留在這一章節講的,當租客聯絡到中介時,這是一個主動的動作,租客是發起方,當和中介建立聯絡後,他們雙方互留電話,同時中介找到合適的房東,並且也留下了聯絡方式,這個時候中介開始等待房東的回應,這期間中介什麼都幹不了,一分鐘一個電話的詢問房東是否考慮好了,那麼中介的下場只有兩個,房東很生氣,一分鐘一個電話,你沒事兒,我還有事兒呢,你等我訊息不行嗎?直接拉黑。或者由於中介一次只能處理一個事情,這件事處理不完,就不能處理下一件事,效率低下被公司開除。租客也是一樣,一次次的去詢問中介,找到房子了嗎?等待他的下場也有兩個,一、一次次的電話,導致電話費報表,二、由於電話費太貴,打算一天問一次,由於獲取訊息不及時,結果房子被別人租走了,也就是訊息的即時性低,而露宿街頭(雖朱門酒肉臭,但別路有凍死骨,願在外漂泊的你們在這寒冷的冬天裡有一個溫暖的所在)。

為了避免上面的悲劇發生,中介公司改善了溝通機制,首先從租戶的角度,通過主動向租客彙報進度來解決訊息即時性的問題,讓租戶第一時間得到最新情況,其次,中介不再催促房東,而是讓房東考慮好後通知中介,當中介收到房東的訊息後第一時間通知給租戶,通過這兩個環節的改造,一條高效的通知鏈就形成了。

為MVC框架增加監聽

Modle的職責是對資料的生產和處理,並在結束一些耗時的操作後,應該主動的通知給Controller,所以Model為被觀察物件,而Controller為觀察物件,它觀察著Model的一舉一動,為了能更好的觀察Model的行為,Controller派了一個“眼線”到Model中,這個“眼線”的職責就是監聽Model的一舉一動。

第一步,定義一個“眼線”

/**我是一個“眼線”
public interface Observer {}
複製程式碼

這裡的眼線就是一個觀察物件的介面,但具體讓它做什麼,我們還不清楚,通過介面的形式未來會有很好的擴充套件性,定義完眼線,如何使用呢?

還記得上一篇中View是被怎麼使用的嗎?它被Actvity實現了,也即是說我們這裡的“眼線”也應該被某個物件去實現,否則它將沒有任何用處,由於是Controller派出了一個“眼線”,所以應該由Controller去使用,使用的兩種途徑,要麼自己具備“眼線”的功能,也就是is a,要麼就是自己招募一個“眼線”,Has a。

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

/**我是一個Contorller,同時我就是個眼線**/
public class TasksController implements Observer{
    void loadNomData() {}
}
複製程式碼

TasksController,通過實現Observer介面,具備了觀察者的能力。

2、我招了個眼線

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

TasksController,通過內部例項化了一個Observer介面,間接的獲得了觀察者的能力。

以上兩種都可以獲得觀察者的能力,但是從擴充套件性來講,還是儘量去選擇第一種方式。

第二步,放置眼線

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

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,通過構造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.setController(controller);
    }
}
複製程式碼

這是上一篇內容中的程式碼段,未來都會圍繞著這段程式碼進行改進,看最下面這一行:

model.setController(controller);
複製程式碼

其實,這一步就是model持有了controller,由於我們現在的controller具備了觀察者的職責,同時在我們真正的使用中沒有必要把整個controller的職責都暴露給model,而model也只需要controller觀察者的能力,好讓它即時的把結果告知controller,所以我們可以這樣改造一下這段程式碼為:

model.addObserver(observer: controller);
複製程式碼

看起來傳的引數依舊是controller,只不過改了一個方法名,這沒什麼區別啊,我想說的是區別還是有的,方法名的改變意味著這段程式碼的業務變了,雖然都是controller,沒改之前是全部的controller,而下面的程式碼是告訴大家,我只使用controller觀察者的部分,其他的我不關心,雖然你全給了我,但用那些是我的事情。

改造過後的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,通過構造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
    }
}
複製程式碼

第三步,傳送事件

這個時候,Model已經獲取到了觀察者,也就是Controller,那麼當Model自己發生變化時,就可以即時的通知給Controller了,我們試著發一個事件,但是在傳送事件前,不要忘了眼線還沒有具體的能力,我們只是定義了一個介面,眼線具體有什麼能力還是要結合具體業務去定義,這不屬於架構的部分,更偏向於業務層,這裡我們就模擬當Model獲取到資料後,通知Controller,我拿到資料了,所以讓眼線有通知資料ok的功能:

/**我是一個“眼線”
public interface Observer {
    //資料OK
    void onDataComplate(Data data);
}
複製程式碼

目前眼線已經準備完畢,就等著Model來使用了,我們用Model來傳送一個事件

Model :TasksRepository

    /**我是一個Model**/
    public class TasksRepository {
        //眼線集中營
        public static ArrayList<Observer> observers = 
            new ArrayList<Observer>();
        viod addObserver(Observer observer){
            observers.add(observer);
        }
        //從伺服器請求獲取資料
        void getTasks() {
            //訪問伺服器,耗時。。。伺服器返回時,
            Data data = fromServer();
            //傳送事件
            for(Observer observer : observers){
                observer.onDataComplate(data);
            }
        }
        //從記憶體快取獲取資料
        Data getTaskCache() {}
        //從磁碟快取獲取資料
        Data getTaskDiskCache(){}
        //儲存一條資料
        boolean saveTask(Task task) {}
        //對資料進行排序
        Data orderData(Data data, int orderType){}
    }
複製程式碼

在實際的開發中,Model可不是隻為了某一個Controller去監聽的,它可以被任何想要監聽它的人監聽,你只要送一個眼線過來,當Modle有變動時,Model會通知所有關心它的人,所以Model裡面有一個Observer的集合:

public ArrayList<Observer> observers = 
            new ArrayList<Observer>();
複製程式碼

當Model發生了變化,就會遍歷這個集合去通知所有的觀察者,而眼線在這裡派上了用場

for(Observer observer : observers){
    observer.onDataComplate(data);
}
複製程式碼

第四步,接收事件

處理事件的特性是觀察者的本質,Controller既然是觀察者,那麼處理事件應該由自己去完成:

Controller :TasksController

/**我是一個Contorller**/
public class TasksController implements Observer{
    //接收事件
    void onDataComplate(Data data) {
        //處理事件
    }
    void loadNomData() {}
}
複製程式碼

TasksController實現了Observer的onDataComplate方法,當Model傳送事件後,onDataComplate方法便能接收到,我們就可以在這裡處理事件了,到此為止整個事件從建立到處理就完成了,這也就是觀察者模式的核心,如果以後需要自己實現一個觀察者模式,那麼就按照上面四個步驟來寫,絕對不會懵圈而且思路會異常的清晰。

那麼View呢?

上面的場景提到過,當房東考慮好後通知給中介,中介會第一時間通知租客結果,那麼具體改如何做呢?

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,通過構造器注入
        TasksController controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
    }
}
複製程式碼

回過頭來看Acivity的程式碼中的這一段:

//初始化Controller,this就是View,通過構造器注入
TasksController controller = 
            new TasksController(tasksView:this);
複製程式碼

首先,通過建構函式,讓controller持有view,當controller接收到model的通知時,緊接著通知view,所以TasksController的程式碼還需改進:

/**我是一個Contorller**/
public class TasksController implements Observer{
    //通過建構函式接收view
    public TasksController(TasksView view) {
        this.view = view;
    }
    //接收事件
    void onDataComplate(Data data) {
        //處理事件,緊接著向view傳送事件
        view.onDataBack(data);
    }
    void loadNomData() {}
}
複製程式碼

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

讓View也介面化

按早之前Controller觀察者化的思路,我們能不能讓view也變成觀察者,當然可以而且是必須的,讓view 去觀察Controller的變化,Controller又去觀察Model的變化,那麼整個鏈式反應就完成了。具體步驟就不分析了,上一個完整的程式碼:

View :TasksView

    /**我是一個View,我本身就是個眼線**/
    public interface TaskView {
        void onDataBack(Data);
        //當列表初始化後,告訴控制器該載入資料了
        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,通過構造器注入
        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){
        //處理事件。。。
    }
    //當列表初始化後,告訴控制器該載入資料了
    void viewCreate(){
        controller.loadNomData();
    }
    //更新列表
    void upDateList(){}
    //just for ui
    void beginLoadData(){}
}
複製程式碼

總結:

這一篇中,我們通過觀察者模式對我們的框架進行了改進,通過監聽,讓MVC的三個物件形成了一個事件傳送帶,事件就好比有了方向一般從Model出發,經過Controller最終流向View,而後期我們可以在這條鏈路上對我們的事件做任何想要做的操作,而最終的接收者View是完全不用關心的,亦或者view可以自定義自己想要的資料,在Model還沒有傳送事件前。說的更確切點,我們可以在事件的傳送前,傳輸中,接收前,這三個點做很多我們希望做的事情,比如資料在接收前的一些排序的轉變,這些我們都會以介面的 方式暴露出來。到此,MVC的介紹結束,但框架的搭建還沒有完成,在接下來的被容裡,我們通過MVP的方式對框架進行進一步的改進,同時加入一些實質些的工具,讓框架具備一些基本的業務功能。

相關文章