從工作中反思mvp設(shè)計(jì)模式

本項(xiàng)目github地址:https://github.com/samonkey-zouyingjun/mvp
轉(zhuǎn)載請注明出處:http://www.reibang.com/p/9cee97587006

反思源于工作巡扇,卻高于工作


MVC和MVP的區(qū)別大家都懂痛悯,簡單的用一副圖片就可以概括乙埃,這種層次的背書應(yīng)付面試還行垄惧,但是不求甚解恐怕永遠(yuǎn)領(lǐng)會不到精髓升略,怎沒有金剛鉆怎攬瓷器活反肋?接下來我通過一些工作中所遇到的問題來談?wù)刴vp設(shè)計(jì)模式的前世今生尼摹,在文章末尾加上對mvp的案例以及分析和措,希望對大家有所幫助,本文案例比較適合mvp初學(xué)者助被,附錄給出了一些進(jìn)階學(xué)習(xí)的建議剖张。

問題一

隨著界面和業(yè)務(wù)邏輯復(fù)雜度不斷提升,Activity的代碼就會越來越用臃腫揩环,一個復(fù)雜一點(diǎn)的Activity常常是幾千行代碼搔弄,維護(hù)起來特別亂,這又是為何丰滑?

1.首先我們來分析一下傳統(tǒng)的mvc模式:

  • Modle層:適合做一些業(yè)務(wù)邏輯處理顾犹,比如數(shù)據(jù)庫存取操作,網(wǎng)絡(luò)操作吨枉,復(fù)雜的算法等耗時的任務(wù)蹦渣。
  • View層:應(yīng)用層中處理數(shù)據(jù)顯示的部分,XML布局可以視為V層貌亭,顯示Model層的數(shù)據(jù)結(jié)果。
  • Controller層:在Android中认臊,Activity處理用戶交互問題圃庭,因此可以認(rèn)為Activity是控制器,Activity讀取V視圖層的數(shù)據(jù)(eg.讀取當(dāng)前EditText控件的數(shù)據(jù))失晴,控制用戶輸入(eg.EditText控件數(shù)據(jù)的輸入)剧腻,并向Model發(fā)送數(shù)據(jù)請求(eg.點(diǎn)擊Button發(fā)起網(wǎng)絡(luò)請求等)。

從上面可以知道MVC在安卓中涂屁,Activity并不是一個標(biāo)準(zhǔn)的Controller(處理用戶的交互請求和響應(yīng))书在,也需要做View層的工作(加載布局和初始化用戶界面),這就導(dǎo)致了V層和Controler層的偶和度較高拆又。

2.再來看看mvp是如何改進(jìn)這一問題的:

  • Modle層:和原來一樣儒旬,適合做一些耗時的業(yè)務(wù)邏輯處理。
  • View層:明確定義為Activity帖族,負(fù)責(zé)UI元素的初始化栈源,建立UI元素與Presenter的關(guān)聯(lián)(Listener之類),同時自己也會處理一些簡單的邏輯(復(fù)雜的邏輯交由 Presenter處理).
  • Presenter層:負(fù)責(zé)復(fù)雜的邏輯處理竖般,對應(yīng)各種實(shí)現(xiàn)類和回調(diào)方法

從MVP模式中我們也可以看到一些明顯的改變甚垦,弱化了Activity的職責(zé),讓其變得和輕薄,只負(fù)責(zé)顯示數(shù)據(jù)艰亮、提供友好界面和交互就行闭翩;其次是在原來Activity和Modle層中又剝離出了各種接口的實(shí)現(xiàn)類,通過回調(diào)來傳遞數(shù)據(jù)迄埃。

3.所以MVP相比MVC的好處:業(yè)務(wù)結(jié)構(gòu)清晰疗韵,而且將來更換實(shí)現(xiàn)類不用修改業(yè)務(wù)結(jié)構(gòu),原因就是presenter就是實(shí)現(xiàn)類调俘,把model和View完全解耦伶棒。壞處就是:分層多了,邏輯會更繞彩库。

問題二

Android應(yīng)用做單元測試肤无,一般都是部署到虛擬機(jī)或者真機(jī)上再模擬操作進(jìn)行測試,而這將耗費(fèi)大量不必要的時間骇钦,如何節(jié)省了不必要的部署和測試時間宛渐?

從問題一的分析我們知道,傳統(tǒng)的mvc模式下Controller層和View層耦合都較高難以分離眯搭,所以一般都是通過部署來測試窥翩。但是再M(fèi)VP模式中,Presenter和Activity中是通過接口來進(jìn)行交互鳞仙,我們只需要去自定義類實(shí)現(xiàn)來這個接口寇蚊,再這個類中來模擬Activity調(diào)用就可以進(jìn)行單元測試,開發(fā)效率大大提高棍好。

從mvp到設(shè)計(jì)模式


1.一句話簡單概括mvp

mvp是安卓中面向接口編程的典型仗岸,presenter通過view和modle接口的引用,來調(diào)用具體實(shí)現(xiàn)類的方法

2.三層架構(gòu)
對于各種架構(gòu)思想借笙,三層架構(gòu)和MVP等模式有異曲同工之妙扒怖。三層架構(gòu)是從整個程序架構(gòu)的角度來分為WEB(界面層)、DAL(數(shù)據(jù)訪問層)和BLL(業(yè)務(wù)邏輯層)各司其職业稼,分工明確盗痒。對于程序員來說也是為了在不同階段更加注重某階段業(yè)務(wù)邏輯處理。

2.萬變不離其宗低散,不管哪種設(shè)計(jì)模式俯邓,其優(yōu)化目的都是:

  • 易于維護(hù)
  • 易于測試
  • 松耦合度
  • 復(fù)用性高
  • 健壯穩(wěn)定

案例與分析


項(xiàng)目結(jié)構(gòu)

此項(xiàng)目分包是根據(jù)功能模塊來分的(登陸和主頁數(shù)據(jù)顯示兩個模塊)。

login中的mvp分塊

Modle層:對應(yīng)于longinInteractorImple實(shí)現(xiàn)了longinInteractor
View層:對應(yīng)于LoginActivity實(shí)現(xiàn)了LoginView接口
Presenter層:對應(yīng)于LoginPreseenterImpl實(shí)現(xiàn)了LoginPresenter接口

login模塊中的mvp實(shí)現(xiàn)

1.先來看看View層都干了那些事情

public interface LoginView {

    void showProgress();

    void hideProgress();

    void setUsernameError();

    void setPasswordError();

    void navigateToHome();

}

public class LoginActivity extends AppCompatActivity implements View.OnClickListener, LoginView {

    private ProgressBar progressBar;
    private EditText username;
    private EditText password;
    private LoginPresenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login); //初始化UI

        progressBar = findViewById(R.id.progress);
        username = findViewById(R.id.username);
        password = findViewById(R.id.password);
        findViewById(R.id.button).setOnClickListener(this); //綁定監(jiān)聽

        presenter = new LoginPresenterImpl(this); //建立UI元素與Presenter的關(guān)聯(lián)
    }


    @Override
    protected void onDestroy() {
        presenter.onDestroy();
        super.onDestroy();
    }


    @Override
    public void onClick(View view) {
        presenter.validateCredentials(username.getText().toString(),password.getText().toString());
    }

    @Override
    public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideProgress() {
        progressBar.setVisibility(View.INVISIBLE);
    }

    @Override
    public void setUsernameError() {
        username.setError(getString(R.string.username_error));
    }

    @Override
    public void setPasswordError() {
        password.setError(getString(R.string.password_error));
    }

    @Override
    public void navigateToHome() {
        startActivity(new Intent(this, MainActivity.class));
    }
}

可以看到現(xiàn)在Activity主要負(fù)責(zé)的就是以下三件事:

  • 初始化UI
  • 綁定監(jiān)聽
  • 建立UI元素與Presenter的關(guān)聯(lián)

2.再來看看Presenter層干了那些事情

public interface LoginPresenter {

    void validateCredentials(String username,String password);

    void onDestroy();
}

public class LoginPresenterImpl implements LoginPresenter, LoginInteractor.OnloginFinishedListener {

    private LoginView loginView;
    private LoginInteractor interactor;

    public LoginPresenterImpl(LoginView loginView) {
        this.loginView = loginView;
        this.interactor = new LoginInteractorImpl();
    }

    @Override
    public void validateCredentials(String username, String password) {
        if(loginView != null){
            loginView.showProgress();
        }

        interactor.login(username,password,this); //關(guān)聯(lián)Modle層
    }

    @Override
    public void onDestroy() {
        loginView = null;
    }

    @Override
    public void onUsernameError() {
        if(loginView != null){
            loginView.setUsernameError();
            loginView.hideProgress();
        }
    }

    @Override
    public void onPasswordError() {
        if(loginView != null){
            loginView.setPasswordError();
            loginView.hideProgress();
        }
    }

    @Override
    public void onSuccess() {
        if(loginView != null){
            loginView.navigateToHome();
        }
    }
}

可以看到P層主要是處理loginActivity傳給LoginPresenterImpl 邏輯業(yè)務(wù)谦纱,并在需要訪問數(shù)據(jù)的時候關(guān)聯(lián)了M層看成。

3.最后來看看Modle層干了那些事情

public interface LoginInteractor {

    interface OnloginFinishedListener{
        void onUsernameError();
        void onPasswordError();
        void onSuccess();
    }

    void login(String username,String password,OnloginFinishedListener listener);

}
public class LoginInteractorImpl implements LoginInteractor {
    @Override
    public void login(final String username, final String password, final OnloginFinishedListener listener) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                boolean error = false;
                if(TextUtils.isEmpty(username)){
                    listener.onUsernameError(); //處理P層傳入的邏輯
                    error = true;
                    return;
                }
                if(TextUtils.isEmpty(password)){
                    listener.onPasswordError();
                    error = true;
                    return;
                }
                if (!error){
                    listener.onSuccess();
                }

            }
        },2000);
    }
}

這登陸模塊的modle層未涉及到數(shù)據(jù)訪問,只是做了模擬操作跨嘉。

4.總結(jié)
總體來說mvp給人的感覺是很爽快的川慌,特別是activity中的書寫更是簡明清爽吃嘿,在activity中只是看到Ui監(jiān)聽的代碼和P層綁定代碼,而具體邏輯則在P層中實(shí)現(xiàn)梦重,P層中涉及到數(shù)據(jù)訪問則綁定M層兑燥,然后在M層中處理相應(yīng)數(shù)據(jù)的封裝,再把結(jié)果給P層琴拧,P處理業(yè)務(wù)后在給V顯示〗低現(xiàn)在再看文章頭的關(guān)系圖是不是更親切了呢?(●'?'●)

總得來說mvp用的不是很多蚓胸,也沒有說非要遵從這個模式挣饥,模式始終都是為程序員服務(wù)的,每種模式都是各有弊利沛膳。對于初學(xué)者來說不建議對大項(xiàng)目用mvp扔枫,可以先從小項(xiàng)目上嘗試使用,熟能生巧锹安。以下附錄提供進(jìn)階建議短荐。

附錄:
Introduction-to-Model-View-Presenter-on-Android 英文翻譯版(MVP經(jīng)典必讀)
Introduction-to-Model-View-Presenter-on-Android 中文翻譯版
ZhiHuMVP(MVP配合RxJava 響應(yīng)式編程)
ActivityFragmentMVP github地址(MVP處理Activity和Fragment,Dagger 注入)
Material-Movies github地址( 使用material design +MVP實(shí)現(xiàn)的Material-Movies)
androidmvp(star2000+的MVP實(shí)例)
MVP for Android: how to organize the presentation layer(star2000+MVP的講解)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叹哭,一起剝皮案震驚了整個濱河市忍宋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌风罩,老刑警劉巖糠排,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異超升,居然都是意外死亡乳讥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門廓俭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唉工,你說我怎么就攤上這事研乒。” “怎么了淋硝?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵雹熬,是天一觀的道長。 經(jīng)常有香客問我谣膳,道長竿报,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任继谚,我火速辦了婚禮烈菌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己芽世,他們只是感情好挚赊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著济瓢,像睡著了一般荠割。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旺矾,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天蔑鹦,我揣著相機(jī)與錄音,去河邊找鬼箕宙。 笑死嚎朽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扒吁。 我是一名探鬼主播火鼻,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雕崩!你這毒婦竟也來了魁索?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤盼铁,失蹤者是張志新(化名)和其女友劉穎粗蔚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饶火,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹏控,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肤寝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片当辐。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鲤看,靈堂內(nèi)的尸體忽然破棺而出缘揪,到底是詐尸還是另有隱情,我是刑警寧澤义桂,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布找筝,位于F島的核電站,受9級特大地震影響慷吊,放射性物質(zhì)發(fā)生泄漏袖裕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一溉瓶、第九天 我趴在偏房一處隱蔽的房頂上張望急鳄。 院中可真熱鬧谤民,春花似錦、人聲如沸攒岛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灾锯。三九已至兢榨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顺饮,已是汗流浹背吵聪。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兼雄,地道東北人吟逝。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像赦肋,于是被迫代替她去往敵國和親块攒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

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