為什麼要先介紹MVC?
如果你要想更佳深刻的理解MVP,並在實際開發中靈活的應用,那麼就要先了解它的低配版MVC,他倆只是一步之遙,先了解MVC再學習MVP,MVP的優勢才能凸顯出來,這樣連貫性的學習才會加深對MVP的理解。
目錄
快速回顧
在上一篇中,我們學習了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的方式對框架進行進一步的改進,同時加入一些實質些的工具,讓框架具備一些基本的業務功能。