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

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

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


MVC和MVP的區(qū)別大家都懂,簡單的用一副圖片就可以概括,這種層次的背書應(yīng)付面試還行,但是不求甚解恐怕永遠(yuǎn)領(lǐng)會不到精髓,怎沒有金剛鉆怎攬瓷器活?接下來我通過一些工作中所遇到的問題來談?wù)刴vp設(shè)計模式的前世今生,在文章末尾加上對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è)計模式


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è)計模式,其優(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容