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

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

目錄

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三個層的基本職責,我們再回顧一下這三個“片段”:

Model層

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

View層

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

Controller

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

同時還遺留了兩個問題:

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

把片段“組合”起來

在模式設計時,我們會通過關係圖來反映物件間的關係,如下圖:

MVC架構圖

這是一張非常經典的MVC架構圖(手殘,有點醜……),相信大家對這張圖已經不陌生了,OK,這張圖我先放下不表,因為考慮到我們介紹的是架構,本身是抽象的,加上如此抽象的圖,怕是不能好好的切入,所以我想盡可能的巨像化這個過程,當大家有一個清晰的理解後,再回過頭來重新審視它。我本來是想放在文章的後半段祭出這張圖的,但想了想有點不妥,原因是我們上期已經通過一整篇用來初探MVC,也該把MVC的架構圖貼出來應一應景了……

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

那麼MVC這三者的關係究竟是怎樣的呢?

我們依舊通過現實中的場景來描述一下MVC三者的關係,讓大家對這三者的關係有一個初期的直觀的理解,起到一個拋磚引玉的效果,廢話不多,場景開始。

場景

想必大家都租過房子,有當租客的體驗,也有一部分人可能還有當過過房東經驗,在當下的社會中,房屋中介是我們租房找房必不可少的環節,我相信大部分人都和中介打過交道,咳咳,那我們就還原一下租房的過程,

首先,確定角色

在這個場景裡包含了三個角色,租客、中介、和房東,

其次,選擇角度

我們從租戶的角度描述一下租房的場景,只考慮正向流程。

再次,場景描述

租客要先找到中介並描述自己需要的房子,中介接到需求後,通過篩選發現有一個房東很適合,於是中介開始與這個房東接觸,詢問一些和房子租金相關的事宜,如果中介覺的合適,他會把反饋告知客戶,如果客戶也覺的合適,中介就會把租客和房東約在店裡進行更進一步的協商,如果能促成合同,那麼到此為止中介完成了現階段的工作,剩下的事情,比如租客向房東每個季度繳納房租,或者詢問房東家裡電器的使用方式,亦或者房東詢問房客租約到期是否續租等等,這些溝通都可以拋開中介去完成,也就是說,不一定需要中介作為他們之間溝通的橋樑,但也並不意味著中介在以後的場景中是不可用的,比如,在租房過程中有一些事情租客和房東有分歧,也可以通過中介來進行協調和溝通。

還有一種中介的形式是,租客和房東是見不到彼此的,全程由中介負責,這種房叫託管房,租客不知道房東是誰,房東也不知道租客是誰,合同都和中介籤,我們現在不討論這種情況。

來個圖描述一下上訴的情景

好隨便的圖

這張圖的目的一來是複述一下上面的場景描述,二來是讓大家直觀的看到它們三者互動的一個關係。

中介、租客、房東,把他們和MVC的三個角色關聯起來

通過上訴場景的描述,和前一篇帖子中介紹的MVC各個物件的職責,想必他們到底誰是誰一目瞭然,我們試著關聯一下這幾個物件

首先看中介,他負責了執行租客的招租要求,並告知房東這些要求,也就是說他作為中介向房東轉告租客的需求,那中介就是我們的Controller。

再次看租客,他是需求的發出者,他比其他幾個物件更積極的提出需求,就好比View,總是再向Controller提出需求,也會更主動的向Model詢問狀態,就像租客一樣,遇到問題,要麼找中介,要麼去詢問房東,所以,在這裡把租客看作是View更加的貼切。

最後只剩下房東這個角色了,最後的Model就分配給房東吧。

接下來我們用MVC的方式來梳理一下中介、租客、房東的關係

1、View To Controller 求租房

MVP那些事兒 (3)……在Android中使用MVC(上)
2、Controller To Model 詢問:出租嗎?
MVP那些事兒 (3)……在Android中使用MVC(上)
3、Controller To View 告知:找到房源
MVP那些事兒 (3)……在Android中使用MVC(上)
4、View To Model 單聊:能便宜嗎?
MVP那些事兒 (3)……在Android中使用MVC(上)
5、Model To View 單聊:沒門兒!

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

上面這五張圖,是把MVC三個物件間的關係,以及案例中的業務流程做了一個“融合”,是一個抽象到具象的“融合”,架構圖中這些帶箭頭的線,是用來解釋物件間關係的,無非就是那些誰依賴誰的解釋,我同時又線上上加入了一些事件,是希望能把上面具象的問題引到抽象的架構中來,在實際中,這樣的融合是不符合常理的,比如你無法用單個業務場景去定義物件間的依賴關係,比如View To Controller,難道只有求租房這個場景才可以這麼用嗎?顯然不是的,包括各個角色中括號後面的稱呼。所以,為了解決通用性的問題,人們把這樣的具象問題抽象成了一種依賴關係。

架構是藍圖,抽象的存在,而框架是具象的,它是為了解決具體某一類問題而設計的。

MVC架構圖是怎麼來的?拋去具象,看本質

具象到抽象三連圖
抽象到最後就是我們開篇的第一張圖。

在某些場合裡,需要我們去介紹我們的軟體架構,這就需要我們具備描述架構的能力,我覺的可以先描述一下具象的問題,再描述向上抽離的步驟,最後陳述抽象的產物,也就是我們的架構,是一個不錯的思路。

應用到實際中

通過上面大篇幅的拋磚引玉,相信大家對MVC的架構有了一個初步的認識,那麼接下來我們開始真正的使用,還記得上一篇我們使用了一個需求:

需求:列表載入資料並且展示

我們依舊使用這個需求,因為前面定義好了三個層的類,接下來要做的是按照MVC的架構把他們拼裝起來:

在Android中使用

開始加入到實際開發階段,我們先建立一個Activity

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製程式碼

緊接著,我們把上一篇定義好的三個MVC物件也加入進來

Model :TasksRepository

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

View :TasksView

    /**我是一個View**/
    public class TaskView {
        //當列表初始化後,告訴控制器該載入資料了
        void viewCreate() {}
        //更新列表
        void upDateList() {}
        //just for ui
        void beginLoadData(){}
    }
複製程式碼

Controller :TasksController

/**我是一個Contorller**/
public class TasksController {
    void loadNomData() {}
}
複製程式碼

現在我們有了Activity,TasksRepository、TasksView、TasksController

有了這些物件,我們就可以進行組合了,我們從什麼地方下手呢?也許我們可以從MVC的架構圖中得到些啟示,雖然架構圖很簡單,但是有箭頭,有方向,在UML中,這些線和箭頭便是記錄著物件間的關係:

MVP那些事兒 (3)……在Android中使用MVC(上)
按照架構圖的提示,我們為TasksRepository、TasksView、TasksController這三個類梳理了一個大致的依賴關係,同時MainActivity被撂到了一邊,並不是它和這三個物件沒有關係,而是它現在還不能直接用,它需要變一下,為什麼這麼說呢,來看例子:

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化MVC
        TasksController controller = new TasksController();
        TasksView view = new TasksView();
        TasksRepository model = new TasksRepository();
        //依賴關係
        controller.setView(view);
        view.setController(controller);
        view.setModel(model);
        model.setController(controller);
        model.setView(view)
        //執行業務
        controller.loadNomData();
    }
}
複製程式碼

我承認,我又開始拋磚引玉了,從這種寫法來看,Activity到是像MVC的容器,它包含了MVC這三個物件,這麼寫有問題嗎?在繼續之前,引用一個概念,Is a…… and Has a

Is a 與 Has a

翻譯過來,就是,我是什麼,和我有什麼

Is a 我是什麼?

//我是什麼動物?我是一隻貓
class Animal implments Cat {
    //喵喵叫
    void miaomiao();
}
複製程式碼

Has a 我有什麼?

//我有什麼動物?我有一隻貓,一隻狗,一隻豹子
class Animal{
    public Animal(Cat cat) {
        //來只貓
        this.cat = cat;
        //來個豹子
        this.baozi = new Baozi();
    }
    //來只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
}
複製程式碼

相信大家看出了其中的差別,回過頭我們看一下MainActivity,它是什麼,它又有什麼,首先它是一個Activity,其次,它有TasksRepository、TasksView、TasksController這三個物件,重點在Has a上,像這樣的依賴耦合度是非常高的,基本沒有擴充套件的餘地,為了降低耦合,可不可以讓MainActivity少幾個Has a?當然可以。

我是一隻貓,我有一隻狗和豹子

//我是什麼動物?我是一隻貓
class Animal implments Cat {
    //喵喵叫
    void miaomiao();
    public Animal() {
        //來個豹子
        this.baozi = new Baozi();
    }
    //來只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
}
複製程式碼

所以我們讓MainActivity變一下身,從MVC這三個物件TasksRepository、TasksView、TasksControlle中選一個變,這樣強耦合的個數從3個變成了2個,那麼選誰好呢? 目前主流的是選擇View,和選擇Controller,大家都是從業務的角度出發去選擇,首先選擇View,因為Activity裡包含了佈局物件,所以因為“離得近”讓Activity變成View,選擇Controller的是因為Activity本身的生命週期可以很好的用來控制業務流程,這樣對Controller來說更加有利,說實話,我並沒有深挖其中哪一個更好,因為我覺的這兩種方案都有他們的側重,還是要從實際的業務角度去考慮問題,那我們突發奇想讓Activity既是View,又是Controller呢?這不就解決變成誰的問題了嗎?個人覺的還是不要的好,你總不能介紹自己的時候說:大家好,我是貓,也是狗,我是男的也是女的,我相信到時候場面一定很混亂,我們還是保持類的職責單一性吧。但沒準那天變成Model玩一下也可以的吧,所以說,目前我先從變成View來開始,後續的章節我們會講解變成Controller該怎麼辦。

更新UML

MVP那些事兒 (3)……在Android中使用MVC(上)
同時更新一下程式碼,我們讓MainActivity實現一個TasksView,賦予它View的職責

public class MainActivity extends AppCompatActivity implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //略……
    }
}
複製程式碼

確定了Activity,我們才開始了第一步,接下來我們就要看一下Controller的初始化,在此之間需要引用一個概念,依賴注入。

依賴注入

什麼是依賴注入?首先解釋一下什麼是依賴,看下面的程式碼:

class Animal{
    public Animal(Cat cat) {
        //來只貓
        this.cat = cat;
        //來個豹子
        this.baozi = new Baozi();
    }
    //來只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
    //貓貓叫一下
    void miaomiao() {
        cat.miaomiao();
    }
    //豹子叫一下
    void aoao() {
        baozi.called();
    }
}
複製程式碼

aoao這個方法內部,呼叫了baozi這個物件叫的方法called(),也就是說,首先Animal這個物件自己是不會叫的,他需要依賴於Baozi物件,Animal依賴Baozi去叫,這個依賴就是Baozi,我們看到Baozi物件的初始化,是在Animal構造器中new出來的。再看一下miaomiao方法內部,依舊是依賴於cat的miaomiao方法去叫,而這個cat物件是從建構函式傳進來的,baozi和cat同樣是Animal的依賴,但獲得的途徑不同,cat的獲得途徑就是注入,而它又是依賴,所以我們稱之為依賴注入,依賴注入的形式除了通過構造器引數外,還可以通過普通方法傳入,如上面的setDog方法,我們注入了一隻Dog。

依賴注入的好處

說到依賴注入的好處,不得不提Java的物件導向的特性中的多型,舉個例子,上面的程式碼中的Cat可以是一個介面,我們在使用Animal類前,可以確定一下Cat的實現類,根據業務需求,接收到的可能是公貓,也可能是母貓,可能是美短,也可能是英短(貓的品種),如果我們把Cat的依賴放在Animal中去初始化,那麼根據業務的變化,可能需要頻繁的更改Animal類。第二點好處,當Cat的構造器發生變化,Animal也需要更改Cat,如果Cat需要另外的依賴,比如貓需要貓糧它才能活下去,那麼Animal也要間接的依賴貓糧,物件間的耦合性就暴露了出來,所以從外部傳入的依賴,Animal無需關心依賴的依賴。

看來依賴注入是非常好的設計手段,那麼回到上面的問題,Contorller的初始化,我們知道Controller是依賴於View的,所以我們需要把View注入到Controller中去。

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);
    }
}
複製程式碼

通過依賴注入,我們把Controller和View的命運繫結在了一起。 說完了View -> Controller,我們還要解決Controller -> View,其實我們可以發現,Controller就是包含在View中的,因為Activity在上面已經變身成了View,而Controller也在Activity中通過new的方式進行了初始化,也就順理成章的成了View的依賴了,只不過它不是通過注入的方式進入到View(MainActivity)中的,那麼有什麼辦法可以做到這一點呢?當然有辦法了,介紹一下依賴注入的第三種方式:通過構建一個Controller的工廠來負責生產Controller的例項,具體就不在這裡細說了,以後講到Dagger時可以和大家詳細討論。

Model的初始化

那麼Model(TasksRepository)的初始化放在哪裡去做呢?以及它與Controller和View的關係又是怎樣的呢?從MVC的架構圖和框架的UML圖裡可以得到答案,Controller和Model之間是一個單向的依賴,也就是Controller -> Model,同時Model與View是一個雙向的依賴,

1、Controller -> Model 2、Model -> View 3、View -> Model

我們只需滿足這三個條件,我們直接這樣,看程式碼:

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);
    }
}
複製程式碼

首先在Activty中,初始化Model,並將View注入到Model裡,這樣,同時滿足了Model -> View 和 View -> Model,其次,通過model的setController方法將上面建立好的Controller注入到Model中,也就是滿足了Controller -> View 這個條件。

階段性總結

到此我們就完成了MVC片段的組合,我們從頭再梳理一下這個過程

實現步驟:

1、Is View,Activity實現View

將Activity賦予View的職責。
複製程式碼

2、Controller -> View ,在Activity中建立Controller的例項。

通知控制器Controller去處理一些事情
接收View的操作,並轉調給Model
複製程式碼

3、View -> Controller,將View注入到Controller。

接收Controller,編輯自己與Model無關的狀態
複製程式碼

4、Model -> View,在Activity中建立Model的例項。

主動詢問Model的狀態,呈現Model的資料
複製程式碼

5、View -> Model,將View注入到Model。

監聽Model的變化,如有變化通知View
複製程式碼

6、Controller -> Model,將Contoller注入到View

監聽Model的變化,如果變化通知Controller
複製程式碼

心路歷程

通過MVC的架構圖,我們嘗試的設計了UML,並且通過Is a ,Has a原則確定了View的歸屬,其次,通過依賴注入原則,確定了依賴建立方式,再次,通過MVC的架構圖與UML確立了Controller,View, Model這三者之間的關係,再結合依賴注入原則完成了整個框架的搭建。

總結

在之前的介紹MVC各個層的職責中,“監聽”這個詞出現的頻率很高,在面嚮物件語言的設計中,“監聽”是一個動作,與它相反的便是“獲取”這個動作了。目前這個框架還是個雛形,並沒有“監聽”的能力,同時包括各個層程式碼的不完善,以及控制反轉等實際性內容的缺失…… 本來想用一章來寫完,但篇幅有些長,看來不得不分了。以前總覺的看別人的帖子挺快的,不一會兒就看完了,但真正自己去碼可是真的慢,主要是擔心描述的不夠清晰,擔心不能把自己的想法很清晰的表達出來,同時又擔心過於清晰的表達會不會又顯得囉裡八嗦,刪了改,改了刪的,總之,我會盡最大努力完成這個系列的,喜歡的就點個贊,鼓勵鼓勵我。

最後的最後,佈置一個家庭作業,看下面三張圖:

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

問題1,圖一和圖二的區別,以及圖二算不算MVC 問題2、MVC可以像圖三那樣去設計嗎?

相關文章