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

目錄

MVP那些事兒(1)……用場景說話設計模式

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

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

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

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

MVP那些事兒(6)……MVC變身爲MVP架構

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

幾天前Google IO大會剛剛落下帷幕,相信這又會在技術圈裏掀起一陣浪潮,不得不說,Google對Android的熱情未曾消減,這對咱們來講但是一如既往的暖心,畢竟這顆大樹養育了很多產業,廢話很少說,帶着這股暖意咱們開啓本章的內容。框架

在此以前,感謝一下讀者,在第四章節文章裏找出了一處筆誤,證實你們認真的去思考了,在此真心感謝,同時錯誤已經在評論區裏糾正。ide

在開始以前,仍是但願你們能閱讀一下以前的文章,雖然每一章都有一個重點,但這終究是一個體系的學習,在上一章MVP那些事兒(6)……MVC變身爲MVP裏,已經爲你們講解了MVP整個框架的搭建思路,那麼在這一章中,讓它變的豐滿一些,讓它有一些實際的用途。工具

目前主流的網絡層實現是基於RxJava和Retrofit,你們估計已經耳根子都被這倆個傢伙磨破了,放心,這裏我不會講解他們的具體使用,那麼在咱們開發中,網絡請求是一個很是重要的環節,在不使用框架時,咱們依舊可使用Retrofit快速的開發出網絡請求的業務代碼,而咱們一旦使用了框架,就要考慮,如何將它們這些工具集成到咱們的框架中,這可不是簡單的引用和封裝,本章會介紹如何設計一個通用的Repository。

Repository的設計

在Google官方的示例中,Model層被具象成爲Repository倉庫,相信不少小夥伴都有看過todo的Demo,包括我以前的文章,命名方式也是沿用了google,這樣你們在理解類的含義的學習成本會變的很低,而更多的精力去關注文章自己所要表達的內容,因此在這裏咱們也用Google的思想去設計Model層和它的實現:

咱們經過一個類圖來表示:

知識點:裝飾設計模式,賦能與遞歸

很顯然,Repository的設計採用了裝飾設計模式,對於裝飾設計模式,它的關鍵詞就是「賦能」,(這詞最近很流行)但這個賦能並非對本體直接進行賦能,而是創造出來一個新的外殼,這個外殼獲取到本體的能力後,能夠對本體能力進行演化,而這個外殼也會把演化後的能力對外暴露,讓其餘外殼對本身的能力進行演化,還有一種狀況,外殼並非演化本體功能,而是又附加了一個全新的功能,想象一下你的手機加了一個保護殼,又加了一個外接電源(對原有電量擴充),又加了一個手柄殼(全新功能)。本體能夠有多個,而基於本體的外殼能夠有多個,而一個外殼再其餘的外殼眼中也多是個本體,想一想都很酷炫,而這樣的既能縱向又可橫向的擴展要比傳統的單繼承和多實現來的舒服的多。關於遞歸,裝飾模式自己也是類遞歸的體現,這裏不詳細闡述。

在Android的開發中咱們必定會去服務器獲取數據和資源,再結合實際的須要是否須要把這些數據緩存在本地,或者對本地數據進行一些操做,而這些功能的統一出口就是Repository,因此前期設計只需考慮這兩個方向,本地和遠程,別忘了因爲使用的是裝飾設計模式,在將來咱們能夠根據具體業務來建立出他們的演化版本,而無需污染本體,咱們徹底能夠獨立這塊業務,而使用Repository的用戶,能夠徹底不用關心這些,這其實也是分層架構的魅力。接下來咱們經過一個簡單的示例去講解這部份內容。

具體設計與實現,讓咱們來設計一個Repository

首先,咱們定義一個DataSource接口,它只有一個職責getObject(),DataSource 能夠看做爲頂級接口,它是一切的起源(始祖)。

一、定義DataSource

public interface DataSource {
    void getOject();
}
複製代碼

基於這個始祖咱們須要定義兩個子類,Remote和Local,咱們先忽略他們的getObject()的具體實現(涉及到具體實現可能會干擾對設計理解),你能夠簡單的想象一下,一個是從遠端獲取數據, 一個是從本地獲取數據:

二、實現一個RemoteDataSource和LocalDataSource

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //具體實現
    }
}
/**
*   Local
**/
public LocalDataSource  implements DataSource{
    void getInstance(){}
    void getOject() {
        //具體實現
    }
}
複製代碼

按照設計,Repository的總體設計思路主要是裝飾模式,而目前爲止只看到了實現,並無所謂的「賦能」,因此咱們這裏還缺失一個主要的角色Repository,它如何去設計?

對外提供統一接口

咱們在開發過程當中,常常能聽到這樣的建議:請對外提供一個統一的接口,這樣之後有改動只須要改動內部邏輯,外部引用就不用改了,同時考慮到具體業務:數據的獲取能夠從遠到或者是本地獲取,那麼何時從遠端獲取?何時從本地獲取?拿去集成在業務代碼中的開發兄弟會去關心嗎?他們只關心,若是這塊的業務邏輯變了,他們要不要修改代碼?這個時候你會很負責的告訴他們,大家無需關心,由於我提供了一個統一接口Repository,業務層開發的兄弟大家只要關心在什麼時機去初始化和調用就好。

三、實現Repository

按照上面的分析,一、讓Repository繼承始祖DataSource,二、讓Repository依賴於 RemoteDataSource和LocalDataSource:

public class Repository implements DataSource{

    private LocalDataSource local;
    private RemoteDataSource remote;
    
    void getInstance(DataSource remote, DataSourc, local){
        this.remote = remote;
        this.local = local;
    }
    
    @Override
    void getOject() {
        if(//邏輯判斷) {
            local.getObject();
        } else {
            remote.getObject();
        }
    }
}
複製代碼

首先Repository也是一個DataSource,它一樣擁有getOject()的能力,而它對getOject()方法的處理,實際上是借用了local和remote的能力,換個角度來看Repository就是remote和local的包裝類,它作的只是實現了某部分業務邏輯:即,何時調用local和何時調用remote。因爲Repository是對外統一接口對象,因此當內部業務邏輯發生改變,外部是無需關心的。

爲了Repository使用方便,咱們建立一個靜態工廠類RepositoryInjection

public class RepositoryInjection {

    public static Repository provideRepository(@NonNull Context context) {
        checkNotNull(context);
        return Repository.getInstance(RemoteDataSource.getInstance(context),
                LocalDataSource.getInstance(context));
    }
}
複製代碼

那麼業務開發的同窗在合適的地方能夠這樣調用:

RepositoryInjection.provideRepository(context).getObject();
複製代碼

此時這個getObject方法究竟是remote的仍是local的,亦或者是阿貓阿狗的,對於業務來講無需關心。

經過對Repository的分析與實現,咱們瞭解了在特定的業務場景下使用裝飾的好處,那麼爲了加深理解,咱們再假設一個場景,嘗試着去擴展一下。

案例場景描述

假設咱們的remote已經知足了基本的網絡請求功能,並正式投入到線上,這個時候安所有門報了一個漏洞,說咱們的加密key在項目裏使用了明文硬編碼,可能會被獲取,這個時候爲了緊急修復,咱們選擇經過接口動態獲取安全key來增長安全級別,也就是說在請求全部的數據前都須要先請求一下key,這個簡單的需求咱們能夠直接改動remote的邏輯以下:

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //一、先獲取key
        getKey();
        //二、再獲取數據
        getData()
    }
}
複製代碼

你很快的完成了這個需求並沾沾自喜,上線運行,過幾天技術經理髮現了一個問題,因爲接口訪問量太大,資源消耗嚴重,決定只對部分接口進行加密,這個時候remote可能沒法獨立去完成這個需求,就須要擴展了,如何擴展?咱們能夠這樣:

首先,把RemoteDataSource改成最初的版本。

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //獲取數據
        getData()
    }
}
複製代碼

其次,定義一個RemoteKeyDataSource類對RemoteDataSource進行包裝

/**
*   New Remote key
**/
public RemoteKeyDataSource  implements DataSource{
    private RemoteDataSource remote;
    void getInstance(DataSource remote){
        this.remote = remote;
    }
    void getOject(){
        //獲取key
        getKey();
        //獲取數據
        remote.getObject();
    }
}
複製代碼

咱們演化的getObject方法首先執行getKey()獲取加密key,而後執行remote的getObject()方法,這樣在不修改remote的前提下功能獲得了演化。

How to use

修改一下Repository

public class Repository implements DataSource{

    private LocalDataSource local;
    private RemoteDataSource remote;
    
    void getInstance(DataSource remote, DataSourc, local){
        this.remote = remote;
        this.local = local;
    }
    
    @Override
    void getOject() {
        if(//邏輯判斷) {
            local.getObject();
        } else if(//須要key){
            //包裝一下remote
            RemoteKeyDataSource.getInstance(remote).getObject();
        } eles {
            //不須要要key
            remote.getObject();
        }
    }
}
複製代碼

而對於業務方的同窗,他們對此次修改是「無感知」的。若是有一天業務場景發生了變化要用回RemoteDataSource,相信那個時候你必定會從容不迫。

總結

在真實的業務場景中,功能擴展是永遠不變的話題,其實模式很好理解,設計也很好去揣摩,但如何去合理的拆解功能是須要大量的經驗去堆砌的,咱們經過裝飾模式來嘗試去分析Repository的設計核心思想,合理的功能劃分加上正確的拓展思路是必不可少的,但願經過這章內容給你們作一個拋磚引玉的做用,設計就是這樣,沒有固定的範式,但必定要掌握其本質。

在Android中也有裝飾模式的身影,好比ContextWrapper對Context的包裝。那麼Repository設計就僅限於此嗎?答案是否認的,一個通用的框架可考慮的點仍是不少的,但願能在將來的章節中有機會就Repository的設計增長几個場景。

相關文章
相關標籤/搜索