MVP 筆記

  • MVP 與 MVC 簡單介紹
  • 實踐

參考資料:
Android MVP 詳解(下)
一步一步實現(xiàn) Android 的MVP框架
如何更高效的使用 MVP 以及官方 MVP 架構(gòu)解析
Android:聊聊我所理解的 MVP

MVP

MVC(圖片來自[ Android:聊聊我所理解的 MVP](http://www.iamxiarui.com/2016/09/20/android%EF%BC%9A%E8%81%8A%E8%81%8A%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84mvp/?utm_source=tuicool&utm_medium=referral))

MVP(圖片來自[ Android:聊聊我所理解的 MVP](http://www.iamxiarui.com/2016/09/20/android%EF%BC%9A%E8%81%8A%E8%81%8A%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84mvp/?utm_source=tuicool&utm_medium=referral))

Model 中有實體類,還負責對數(shù)據(jù)的獲取化焕,數(shù)據(jù)的解析,數(shù)據(jù)的存儲肆饶,數(shù)據(jù)的分發(fā)淳地,數(shù)據(jù)的增刪改查等操作。涉及到數(shù)據(jù)操作都是在model進行的。

Presenter 負責把view交給自己的命令進行一定的校驗等操作交給model處理,會把model處理的結(jié)果交給view

MVC 的問題在于在某種程度上 Activity 扮演了 View 和 Controller 的角色敞咧。
MVP 實現(xiàn)更好的解耦,提高代碼的靈活性辜腺、使得代碼層次清晰休建,而代價就是代碼量可能更多。

MVP 的實踐

參考 Google 的 todo-mvp哪自,這個官方的項目可不止于 MVP丰包。
實際上實現(xiàn) MVP 這種架構(gòu)的方法是很多的,這里我也只是將自己的理解寫出來壤巷,
如果有什么不對的地方邑彪,或者有更好的方法,歡迎討論胧华。
項目核心文件:

核心文件

MainContract :將 Presenter 和 View 的接口都放到這里寄症,增加可讀性
MainController :有時候是 MainActivity 或者 MainFragment
MainPresenter :處理業(yè)務邏輯和數(shù)據(jù)模型等
MainView :只需要接受 Presenter 的控制,做一些初始化的工作

Model 在這里并沒有對應的文件矩动,實際上和 MVC 中的 Model 沒有太多區(qū)別

放一張自己做的關(guān)于我理解的 MVP 的一些核心文件之間的關(guān)系:


MVP(圖片來自[ SoftXJ](http://www.reibang.com/u/3e8115dfb254))

下面是詳細的解析每個文件:

MainContract


interface MainContract {
    abstract class View extends BaseView<Presenter>{
        
        //Presenter 在獲取數(shù)據(jù)成功后調(diào)用這個方法來設置 View 中的 RecyclerView
        abstract void setNewsRecyclerView(News news);
        
        //Presenter 在獲取數(shù)據(jù)成功后要調(diào)用這個方法有巧,通知 View 已經(jīng)完成了數(shù)據(jù)的更新
        abstract void notifyRefreshSuccess();
        
        //同上
        abstract void notifyRecyclerChanged();
    }

    abstract class Presenter extends BasePresenter<View>{

        public Presenter(View view) {
            super(view);
        }
        
        //View 中調(diào)用這個方法,讓 Presenter 去在線的獲取數(shù)據(jù)
        abstract void getNewsOnline(String url);
        
        //View 中調(diào)用這個方法悲没,讓 Presenter 加載更多的數(shù)據(jù)
        abstract void loadMoreNews(News news);
        
        //View 中調(diào)用這個方法篮迎,讓 Presenter 查看當前數(shù)據(jù)是否最新
        abstract void checkIsLatest(String url);
    }
}

這個文件在我的理解中是可以沒有的,但添加了之后代碼就會更加的清晰示姿。
畢竟 MVP 結(jié)構(gòu)中每個 Activity/Fragment 都跟著若干的 Presenter 和 View甜橱,某種意義上也是比較的臃腫。
那如何利用這樣的接口類讓整個項目變得清晰呢栈戳?

  • 首先岂傲,記得讓我們的 MainView、MainPresenter 繼承這個接口中的 View 和 Presenter子檀。
  • 記得在 View 獲取 Presenter 的實例的時候镊掖,應該獲取 Contract.Presenter,而不是具體的實現(xiàn)的類褂痰。反之亩进,在 Presenter 獲取 Contract.View。
MVP-P

這么做的目的是為了讓 View 和 Presenter 只能調(diào)用對方在 Contract 中定義的方法
這時候就不能隨意調(diào)用對方的方法缩歪,哪怕是 public 方法镐侯,
一來看源碼的時候就很清晰的知道 View 和 Presenter 都有哪些操作。
一來促使我們在 Contract 寫方法的時候認真想一想這些方法是否有必要讓對方調(diào)用。

BaseView 和 MainView

注意苟翻, MainContract 中的 View 會繼承這個 BaseView韵卤,而不是給 MainView 直接繼承的
BaseView 封裝一些每個 View 都會有的方法崇猫,提高復用沈条。
這里的 View 是繼承自 Fragment,傳入的 Presenter 不確定诅炉,所以用泛型類 T
部分代碼如下:

public abstract class BaseView<T extends BasePresenter> extends Fragment {
    public Activity mActivity;
    private T mPresenter;

    /**
     * 初始化presenter蜡歹,方便view層操作presenter來操作數(shù)據(jù)
     *
     * @param presenter presenter對象
     */
    public void setPresenter(T presenter) {
        mPresenter = presenter;
    }

    /**
     * 用于加載需要加載的layout視圖
     *
     * @return layout's id
     */
    public abstract int viewLayoutId();

    /**
     * 此方法用于初始化 View 視圖。當 Fragment 的 onCreateView()方法被調(diào)用時涕烧,會回調(diào)此方法月而。
     *
     * @param view 通過 viewLayoutId 獲得父視圖
     */
    public abstract void initView(@Nullable View view);


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = getActivity();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        if (viewLayoutId() == 0) {
            initView(null);
            return super.onCreateView(inflater, container, savedInstanceState);
        }

        View view = inflater.inflate(viewLayoutId(), container, false);
        initView(view);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mPresenter != null) {
            mPresenter.start();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mPresenter != null) {
            mPresenter.destory();
        }
    }

}

MainView,在這里并沒有展示太多的操作议纯,主要是針對特定的頁面的一個初始化父款。
相信很多人一開始會一直想,什么操作放到 View瞻凤,什么操作放到 Presenter 呢憨攒?
在實際開發(fā)中,比如彈出一個 AlertDialog阀参,針對 dialog 的一些初始化自然是在 View 中肝集,而顯示的數(shù)據(jù)則通過調(diào)用 mPresenter 的方法獲取,獲取之后顯示的位置等在 View 設置蛛壳。
再比如 RecyclerView杏瞻、ViewPager 等的滑動監(jiān)聽的事件自然在 View,等等衙荐。


public class MainView extends MainContract.View {

    private TextView mTextView;
    private Button mButton;
    private MainContract.Presenter mMainPresenter;

    @Override
    public void showText(String text) {
        mTextView.setText(text);
    }

    @Override
    public int viewLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    public void setPresenter(NewsContract.Presenter presenter) {
        super.setPresenter(presenter);
        mMainPresenter = presenter;
    }

    @Override
    public void initView(@Nullable View view) {
        if (view != null) {
            mTextView = (TextView) view.findViewById(R.id.text);
            mButton = (Button) view.findViewById(R.id.test);
            mButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    openActivity(TestController.class);
                }
            });
        }

    }
}

BasePresenter 和 MainPresenter

BasePresenter 同樣是封裝好一些常用的方法

public abstract class BasePresenter<V extends BaseView> {

    private V mView;

    public BasePresenter(V view) {
        mView = view;
        mView.setPresenter(this);
    }

     /**
     * 獲得View的對象
     *
     * @return 與Presenter綁定在一起的view對象
     */
    public V getView() {
        return mView;
    }

    /**
     * 生命周期開始回調(diào)
     */
    public abstract void start();

    /**
     * 生命周期結(jié)束回調(diào)
     */
    public abstract void destory();
}

MainPresenter 中實現(xiàn) View 中的一些業(yè)務邏輯捞挥,比如請求數(shù)據(jù)、處理數(shù)據(jù)等需要操作赫模,
下面只是部分的代碼树肃,值得一提是 Presenter 中通過構(gòu)造函數(shù)獲取 View 的實例蒸矛,View 中通過setPresenter()獲取瀑罗。

public class NewsPresenter extends NewsContract.Presenter {

    private NewsContract.View mView;

    public NewsPresenter(NewsContract.View view, NewsSource newsSource) {
        super(view);
        mView = view;
    }
   /**
     * 初始化頁面時被調(diào)用
     * 會判斷加載的數(shù)據(jù)是否已經(jīng)加載過
     * @param url
     */
    @Override
    void getNewsOnline(String url) {
        News news = mNewsSource.getNewsToCacheWithTime(time);
        if (news != null){
            mView.setNewsRecyclerView(news);
            return;
        }
        //如果緩存內(nèi)沒有數(shù)據(jù)再去加載
        mNewsSource.getNews(url,new ResultCallback<News>() {
            @Override
            public void onSuccess(final News data) {
                mNewsSource.saveNewsToCache(data);
                mView.setNewsRecyclerView(data);
            }
            @Override
            public void onFail(String msg) {
                LogUtil.e("fail:" + msg);
            }
        });


    }

BaseController 和 MainController

其實 Controller 一般來說是對應我們說的 MVC 的 C。
一般情況下雏掠,MVP 的 V 對應的可以是 Activity / Fragment 等斩祭,這里是因為項目結(jié)構(gòu)的原因,我寫的 View 都是繼承的 Fragment乡话,所以額外添加了一個 Controller 來控制摧玫、聯(lián)系 View 和 Presenter。

BaseController 控制 Presenter 和 View 層,統(tǒng)一界面

public abstract class BaseController<V extends BaseView, P extends BasePresenter> extends BaseActivity {

    private V mView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mView = createBaseView();
        createBasePresenter(mView);
    }

    /**
     * 生成一個最基本的view
     *
     * @return view
     */
    protected abstract V createBaseView();

    /**
     * 通過baseView返回一個basePresenter
     *
     * @param baseView 最基本的view
     * @return 最基本的basePresenter
     */
    protected abstract P createBasePresenter(V baseView);
}

MainController 在 createBaseView() 返回了一個 MainView 的實例诬像,這個實例會先傳遞到 BaseController 的 onCreate()屋群,然后被傳到下面的 createBasePresenter() 中,然后就傳遞給了 MainPresenter 中坏挠。

public class MainController extends BaseController<MainContract.View,MainContract.Presenter> {
    
    @Override
    protected MainContract.View createBaseView() {
        return new MainView();
    }

    @Override
    protected MainContract.Presenter createBasePresenter(MainContract.View baseView) {
        return new MainPresenter(baseView);
    }
}

MVP 的 Model 層

經(jīng)過一段時間的摸索芍躏,現(xiàn)在我的項目架構(gòu)變成了這樣:

有 model 的 MVP

在 module 包里,根據(jù)模塊分包降狠,每一個模塊都有一個獨立的 model 包对竣。
這樣我們閱讀源碼的時候,就不需要在不同的包里尋找對應的文件了榜配。

但這只是我自己學習總結(jié)下來的否纬,不一定好,還是要看自己的理解蛋褥。

以一個登錄操作作為例子临燃,事件的傳遞:

例子

·

后續(xù)

在學習 MVP 的時候,經(jīng)常會有一個問題壁拉,為什么要繞一個圈子要實現(xiàn)一個功能呢谬俄?
而且使用 MVP 的時候總會多出很多文件,寫下了很多接口弃理,對 于之前沒有研究過的人來說上手會有一定的難度溃论。
但在開發(fā)中我們總是不可避免的去修改和完善我們的代碼,假如我們的代碼的結(jié)構(gòu)不清晰痘昌,又或者耦合太高钥勋,就會給修改的過程添加很多麻煩。
更別說現(xiàn)在我們經(jīng)常要和別人合作來完成一個項目辆苔,這對我們的代碼規(guī)范和清晰度的要求就更高了算灸,而 MVP 就基本可以滿足我們的要求。

因為自己的項目其實還是很雜亂驻啤,也不合適開源菲驴,所以這里都只是放了部分的源碼,有時間專門開一個項目完善關(guān)于 MVP 的研究骑冗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赊瞬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贼涩,更是在濱河造成了極大的恐慌巧涧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遥倦,死亡現(xiàn)場離奇詭異谤绳,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門缩筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來消略,“玉大人,你說我怎么就攤上這事瞎抛∫杉螅” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵婿失,是天一觀的道長钞艇。 經(jīng)常有香客問我,道長豪硅,這世上最難降的妖魔是什么哩照? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮懒浮,結(jié)果婚禮上飘弧,老公的妹妹穿的比我還像新娘。我一直安慰自己砚著,他們只是感情好次伶,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稽穆,像睡著了一般冠王。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舌镶,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天柱彻,我揣著相機與錄音,去河邊找鬼餐胀。 笑死哟楷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的否灾。 我是一名探鬼主播卖擅,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼墨技!你這毒婦竟也來了惩阶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤健提,失蹤者是張志新(化名)和其女友劉穎琳猫,沒想到半個月后伟叛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體私痹,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了紊遵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片账千。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暗膜,靈堂內(nèi)的尸體忽然破棺而出匀奏,到底是詐尸還是另有隱情,我是刑警寧澤学搜,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布娃善,位于F島的核電站,受9級特大地震影響瑞佩,放射性物質(zhì)發(fā)生泄漏聚磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一炬丸、第九天 我趴在偏房一處隱蔽的房頂上張望瘫寝。 院中可真熱鬧,春花似錦稠炬、人聲如沸焕阿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暮屡。三九已至,卻和暖如春毅桃,著一層夾襖步出監(jiān)牢的瞬間栽惶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工疾嗅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留外厂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓代承,卻偏偏與公主長得像汁蝶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子论悴,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,787評論 25 707
  • 轉(zhuǎn)載至:http://www.reibang.com/p/9a6845b26856 “Android MVP 詳解...
    SnowDragonYY閱讀 10,320評論 5 241
  • 作者:李旺成 時間:2016年4月3日 “Android MVP 詳解(下)”已經(jīng)發(fā)布掖棉,歡迎大家提建議。 MVP ...
    diygreen閱讀 128,841評論 86 1,321
  • 忙年的時節(jié)里,總會自帶一雙挑剔的眼睛看世界察纯,看哪都是臟亂差帕棉,想用我那雙勤勞的雙手來個大清洗针肥,內(nèi)心還自帶配音環(huán)繞立體...
    Miss蘇朵兒閱讀 703評論 0 0
  • 我今天在外面跑了一天,下午4點多才下了車回到居住的城市香伴!快走到家了慰枕,路過一個社區(qū)的門口,一小孩子大概7.8歲左右即纲,...
    楊嘻嘻閱讀 627評論 3 2