MVP模式之TheMVP的使用和擴展

正所謂打鐵要乘熱,今天開我們開始探究MVP的第二階段,來看一看MVP模式裏面我所用到過的一個變種——TheMVP。

如果對MVP還完全不瞭解的童鞋,請移步

MVP模式探索——初識

想要細緻的瞭解theMvp,請參考原文鏈接,且一定要看到最後

用MVP架構開發Android應用

爲什麼強調要看到最後?因爲作者有些補充,這些補充可以讓你清醒的認識到自己的項目是否適合theMVP模式開發以及theMVP模式的一些缺點和限制。

TheMVP的出現原因

MVP模式雖然被大力推廣和使用,但是他必然也是有缺點的,所以纔有了這麼多MVP模式的擴展和變種。傳統MVP模式在Android開發中的缺點大概有以下幾點

  1. 當應用進入後臺且內存不足的時候,系統是會回收這個Activity的。通常我們都知道要用OnSaveInstanceState()去保存狀態,用OnRestoreInstanceState()去恢復狀態。 但是在我們的MVP中,View層是不應該去直接操作Model的,這樣做不合理,同時也增大了M與V的耦合。

  2. 界面複用問題。通常我們在APP最初版本中是無法預料到以後會有什麼變動的,例如我們最初使用一個Fragment去作爲界面的顯示,後來在版本變動中發現這個Fragment越來越龐大,而Fragment的生命週期又太過複雜造成很多難以理解的BUG,我們需要把這個界面放到一個Activity中實現。這時候就麻煩了,要把Fragment轉成Activity,這可不僅僅是改改類名的問題,更多的是一大堆生命週期需要去修改。例如參考文章2中的譯者就遇到過這樣的問題。

  3. Activity本身就是Android中的一個Context。不論怎麼去封裝,都難以避免將業務邏輯代碼寫入到其中。

既然知道了這些問題,我們的解決辦法自然是不要將Activity作爲View層而去單獨包含Presenter類進來。反過來,我們將Activity(Fragment)作爲Presenter層的代碼,包含一個View層的類來。如果你同時是一名IOS開發者,你一定會很熟悉,這不就是ViewController和APPDelegate嗎。

使用Activity作爲Presenter的優點就在於,可以原封不動的使用Activity本身的生命週期去處理項目邏輯,而不需要強加給另一個包含類,甚至記憶額外自定義的生命週期。

而同時作爲獨立的View層,我們的視圖可以原封不動的傳遞給Presenter(不管是Activity或者Fragment),而不需要改任何代碼。對於一個開發團隊,完全可以將View層的東西交給一個人編寫,而將業務實現交給另一個人編寫。而隨着邏輯變化對View的更改,只需要通過Presenter層的包含一個代理對象————ViewDelegate來操作相應的更改方法就夠了。

theMVP的原理

與傳統androidMVP不同(原因上文已經說了),TheMVP使用Activity作爲Presenter層來處理代碼邏輯,通過讓Activity包含一個ViewDelegate對象來間接操作View層對外提供的方法,從而做到完全解耦視圖層。如下圖

這裏寫圖片描述

這裏寫圖片描述

theMVP的代碼實現

An Android MVP Architecture Diagram Framwork

關於theMVP的相關信息,大概就介紹到這裏,本來我打算闡述一下我在項目裏使用theMvp的過程和修改,但是我回過頭去看以前的項目的時候,發現有很多地方其實變化不大,還有就是theMVP使用起來本身就很簡單,創造者在他的demo裏也寫了很多使用方式,我就不再這裏班門弄斧了。

下面着重談一下我對theMVP模式遇到的一個問題的嘗試解決過程,就是一個View需要對應多個Model的時候該怎麼辦?由於我水平有限,只能給出自己修改的解決方案,可能有很多地方的設計是有問題的,如果有大牛們無聊了翻看到這篇文章,希望大牛們給點寶貴的建議,讓我成爲一個更好的碼農,感激涕零。

先看一下我改造過後的theMVP模式的原理圖

這裏寫圖片描述

theMVP模式的創造者在他的文章裏面提到了這個問題,他頁提到了解決方法,使用集合,但是他似乎並沒去嘗試,或者嘗試了,只是太忙了,沒時間分享給大家而已。雖然我想給這個模式改個名字,可是還是算了吧,畢竟不屬於我的創作,我只是個搬運工而已。所以,後面還是叫他theMVP模式吧。

改造之後的theMVP模式裏,Presenter不再持有一個DataBinder,而是持有多個,每個DataBinder按照不同Model的改變通過Delegate操作View,這樣就解決了一個View需要多個Mode的問題。

正所謂:Talk is cheap,show me the code.實現這個模式的方法千萬種,下面我只是給大家一個我實現該模式的一個參照,希望大家不要嫌棄,也不要噴我代碼寫的不好,畢竟,我還是個在繼續努力學習中的菜鳥程序員。

先看看包結構的變化(請忽略我蹩腳的命名)

這裏寫圖片描述

首先加入了兩個註解 ModelBinderRouter、ViewBinderRouter

ModelBinderRouter
這裏寫圖片描述

只是聲明Model需要的DataBinder的class,便於在Model變化的時候,Presenter找到對應的DataBinder操作View。

ViewBinderRouter

這裏寫圖片描述

這裏聲明瞭Presenter需要包含的Delegate和DataBinder的class,以便於Presenter在合適的時候創建和關聯對應的Delegate和DataBinder。

其次,改動最大的Presenter實現類(太長的類只能貼代碼,mac上居然沒有可以滾動截取AndroidStudio內容的軟件)

ActivityPresenter

public abstract class ActivityPresenter<T extends IDelegate> extends AppCompatActivity {
    protected Map<String, DataBinder> binderMap ;
    /**
     * 視圖代理
     */
    protected T viewDelegate;

    public ActivityPresenter() {
        initDataBinderAndViewDelegate();
    }

    /**
     * 初始化綁定代理
     */
    private void initDataBinderAndViewDelegate() {
        ViewBinderRouter router = getClass().getAnnotation(ViewBinderRouter.class);
        if (null == router) {
            throw new RuntimeException("ViewBinderRouter is invalid");
        }
        try {
            if (null == viewDelegate) {
                Class<? extends IDelegate> viewClazz = router.viewDelegate()[0];
                viewDelegate = (T) viewClazz.newInstance();
            }
            if (null == binderMap) {
                binderMap = new LinkedHashMap<>();
                for (Class<? extends DataBinder> clazz : router.dataBinder()) {
                    DataBinder dataBinder = clazz.newInstance();
                    binderMap.put(clazz.getSimpleName(), dataBinder);
                }
            }
        } catch (InstantiationException e) {
            throw new RuntimeException("create DataBinder failure", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("create DataBinder failure", e);
        }
    }

    protected DataBinder getDataBinder(IModel model) {
        ModelBinderRouter modelRouter = model.getClass().getAnnotation(ModelBinderRouter.class);
        if (null == modelRouter) {
            throw new RuntimeException("find ModelBinderRouter failure");
        }
        return binderMap.get(modelRouter.value().getSimpleName());
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewDelegate.create(getLayoutInflater(), null, savedInstanceState);
        setContentView(viewDelegate.getRootView());
        viewDelegate.initBaseView();
        viewDelegate.initWidget();
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (viewDelegate == null || null == binderMap) {
            initDataBinderAndViewDelegate();
        }
    }
   
    @Override
    protected void onDestroy() {
        viewDelegate = null;
        binderMap.clear();
        super.onDestroy();
    }

    public void notifyModelChange(IModel model) {
        DataBinder dataBinder = getDataBinder(model);
        if (null == dataBinder) {
            throw new RuntimeException("Can not find DataBinder,just check your Presenter's annotation");
        }
        dataBinder.notifyModelChange(viewDelegate, model);
    }
}

增加了一個binderMap,存儲Presenter所關聯的DataBinder.由於DataBinder不再單一,所以如何根據某個Model的改變去調用對應的DataBinder就成了問題,我這裏使用的方法是給Model增加一個關於DataBinder的註解,在某個Model改變的時候,Presenter只需要根據Model的註解,獲取到對應的DataBinder,然後操作Delegate即可。

FragmentPresenter和ActivityPresenter類似,我Github的demo裏有完整代碼,這裏就不贅述了。

接下來我們來看一個簡單的實現(github demo theMvp裏都有)

github項目地址:themvpapp

Activity-Presenter

這裏寫圖片描述

這個很簡單啦,聲明註解,構造Model,根據Model改變操作Delegate。

Delegate

這裏寫圖片描述

這個就更簡單啦,初始化View,定義一些操作View的方法供外部調用等等。

兩個DataBinder

ArticleDataBinder

這裏寫圖片描述

ColorDataBinder

這裏寫圖片描述

兩個Model

Article

這裏寫圖片描述

ColorModel

這裏寫圖片描述

最後實現的效果就是,當Article屬性改變,Presenter會找到ArticleDataBinder去操作Delegate,改變顯示的長文本內容或者SnackBar的內容;當ColorModel改變的時候,Presenter會找到ColorDataBinder去操作Delegate,改變顯示的長文本內容的字體顏色。到這裏,算是解決了一個View對應多個Model,Model之間互不關聯的需求吧。完整的實現請移步去我的github看代碼。

這個設計裏面還有一些已知的可以優化的地方。一個就是Delegate裏綁定View的時候,可以使用ButterKnife註解;還有一個是,Presenter里根據Model的註解去找到對應的DataBinder的時候,可以做一個二級緩存或者用別的方法實現。如果大家有興趣,可以去試驗一下,到時候告訴我,讓我也長進長進,由衷感謝。

前面我說了,我水平有限,所能想到的解決一個View對應多個Model的辦法就是上面這樣的了,其正確性、擴展性、可實施性還有待考驗,希望大家在看到本文時,給出你們寶貴的意見,謝謝大家。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章