Android:MVP模式

简介

关于MVP的介绍网上有很多,这里不再累述,虽然大家的实现方式也许各不相同,但是有一些基本的共识:

  • Model:数据模型;
  • View:用户界面,一般对应于Activity、Fragment;
  • Presenter:作为View和Model的桥梁,实现两者解耦。

MVP的核心是:将View中与UI无关的部分逻辑转移到Presenter中。

其依赖关系如下: 
View与Presenter相互依赖,Presenter依赖于Model,View和Model解耦。 


实例

经典的登陆例子 

Model

数据来源,一般是从网络或者数据库异步加载通过接口回调。我们从内存模拟,然后通过handler#postDelayed来模拟加载过程。

publicclassLoginModel {

    publicstatic Map<String, String> maps = new HashMap<>();
    private Handler mHandler = new Handler();

    static {
        maps.put("key1", "password1");
        maps.put("key2", "password2");
        maps.put("key3", "password3");
    }

    publicvoid getPassword(final String username, final OnGetPasswordListener listener) {
        mHandler.postDelayed(new Runnable() {
            @Overridepublicvoid run() {
                String password = maps.get(username);
                if (password == null) {
                    listener.onGetError();
                } else {
                    listener.onGet(password);
                }
            }
        }, new Random().nextInt(2000));
    }

    publicvoid addUser(final String username, final String password, final OnRegisterListener registerListener) {
        mHandler.postDelayed(new Runnable() {
            @Overridepublicvoid run() {
                try {
                    if (LoginModel.maps.containsKey(username)) {
                        registerListener.onUserExist();
                    } else {
                        LoginModel.maps.put(username, password);
                        registerListener.onSuccessful();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    registerListener.onNetError();
                }
            }
        }, new Random().nextInt(2000));
    }

    publicinterfaceOnGetPasswordListener {
        void onGet(String password);

        void onGetError();
    }

    publicinterfaceOnRegisterListener {
        void onSuccessful();

        void onUserExist();

        void onNetError();
    }
}

View

首先明确一个View的职责,以LoginActivity为例,他要做的是:

  • 用户点击登陆:

    1. 检查账号密码合法性;
    2. 登陆过程给用户提示;
    3. 如果登陆成功,用户提示消失,执行其他操作;
    4. 如果登陆失败,用户提示消失,执行其他操作;
  • 用户点击清除:清除已经输入的内容

  • 用户点击注册:跳转到注册界面

我们将其抽象为一个接口:

publicinterface ILoginView {

    void usernameIllegal();

    void passwordIllegal();

    void showProgress();

    void dismissProgress();

    void loginSuccessfully();

    void loginUnsuccessfully();

    void clearState();

}
  • 17

然后View去实现上诉接口:

publicclassLoginActivityextendsAppCompatActivityimplementsILoginView {

    @InjectView(R.id.et_username)
    EditText mEtUsername;
    @InjectView(R.id.et_password)
    EditText mEtPassword;
    @InjectView(R.id.btn_login)
    Button mBtnLogin;
    @InjectView(R.id.btn_clear)
    Button mBtnClear;
    private ProgressDialog mProgressDialog;

    private LoginPresenter mLoginPresenter = new LoginPresenter(this);

    @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        mProgressDialog = new ProgressDialog(this);
        mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Overridepublicvoid onClick(View v) {
                mLoginPresenter.doLogin(mEtUsername.getText().toString(), mEtPassword.getText().toString());
            }
        });

        mBtnClear.setOnClickListener(new View.OnClickListener() {
            @Overridepublicvoid onClick(View v) {
                mLoginPresenter.doClear();
            }
        });
    }


    @Overridepublicvoid showProgress() {
        mProgressDialog.show();
    }

    @Overridepublicvoid dismissProgress() {
        mProgressDialog.dismiss();
    }

    @Overridepublicvoid loginSuccessfully() {
        Toast.makeText(LoginActivity.this, "login successfully", Toast.LENGTH_SHORT).show();
    }

    @Overridepublicvoid loginUnsuccessfully() {
        Toast.makeText(LoginActivity.this, "login unsuccessfully, please check", Toast.LENGTH_SHORT).show();
    }

    @Overridepublicvoid clearState() {
        mEtUsername.getText().clear();
        mEtPassword.getText().clear();
    }

    @Overridepublicvoid usernameIllegal() {
        mEtUsername.setError("should not be empty");
    }

    @Overridepublicvoid passwordIllegal() {
        mEtPassword.setError("should not be empty");
    }

    @Overridepublicboolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        returntrue;
    }

    @Overridepublicboolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        if (id == R.id.action_settings) {
            startActivity(new Intent(this, RegisterActivity.class));
            returntrue;
        }

        returnsuper.onOptionsItemSelected(item);
    }
}
  • 91

可以看到上诉的View只执行UI相关的操作,没有任何逻辑,逻辑都转移到了Prensenter。有一些比较简单的逻辑可以不必转移到Presenter Layer,比如clearState,这一逻辑完全可以直接在View层调用而不经过Presenter回调。还有输入合法性判断,只判断其是否为空也是很简单的逻辑,同样可以在View层实现。总而言之,简单的逻辑可以在View中处理。


Presenter

最后也是最关键的,在Presenter中连接View与Model,承担View的大部分逻辑。

publicclass LoginPresenter {

    private ILoginView mLoginView;

    private LoginModel mModel = new LoginModel();

    public LoginPresenter(ILoginView loginView) {
        mLoginView = loginView;
    }


    publicvoid doLogin(String inputUsername, final String inputPassword) {
        if (inputUsername.equals("")) {
            mLoginView.usernameIllegal();
            return;
        }
        if (inputPassword.equals("")) {
            mLoginView.passwordIllegal();
            return;
        }
        mLoginView.showProgress();
        mModel.getPassword(inputUsername, new LoginModel.OnGetPasswordListener() {
            publicvoid onGet(String password) {
                if (inputPassword.equals(password)) {
                    mLoginView.loginSuccessfully();
                } else {
                    mLoginView.loginUnsuccessfully();
                }
                mLoginView.dismissProgress();
            }

            publicvoid onGetError() {
                mLoginView.loginUnsuccessfully();
                mLoginView.dismissProgress();
            }
        });
    }

    publicvoid doClear() {
        mLoginView.clearState();
    }
}

比较

假设没有使用MVP架构,常见的做法会是:

mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Overridepublicvoid onClick(View v) {
    if (mEtUsername.getText().toString().equals("")) {
        usernameIllegal();
        return;
    }
    if (mEtPassword.getText().toString().equals("")) {
        passwordIllegal();
        return;
    }
    LoginModel loginModel = new LoginModel();
    showProgress();
    loginModel.getPassword(mEtUsername.getText().toString(), new LoginModel.OnGetPasswordListener() {
        publicvoid onGet(String password) {
            if (mEtPassword.getText().toString().equals(password)) {
                loginSuccessfully();
            } else {
                loginUnsuccessfully();
            }
            dismissProgress();
        }

        publicvoid onGetError() {
            loginUnsuccessfully();
            dismissProgress();
        }
    });
}
});
  • 30

这种写法,虽然一开始写起来会非常方便,但是越到后面越臃肿而难以维护。所以使用MVP架构会使得层次清晰,View与Model解耦,代码也较为整洁。 
假设现在我要测试一下逻辑是否有误,如果是上诉写法,除了run一下工程然后手动点击测试之外,仿佛无从下手。使用了MVP架构,将View职责抽象成接口后,我们写起单元测试会比较方便。

最后,很多文章会把Model和Prensenter也抽象出一个接口,目前我不知道这样做有什么好处,所以我倾向于Model和Presenter先不需要抽象接口,需要时再进行重构。

项目地址: 
https://github.com/leelit/android-mvp-pattern

参考: 
http://blog.csdn.net/vector_yi/article/details/24719873 
https://segmentfault.com/a/1190000003927200 
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0227/2503.html 
http://blog.csdn.net/lmj623565791/article/details/46596109 
https://github.com/antoniolg/androidmvp

猜你喜欢

转载自blog.csdn.net/ElevenDGQ/article/details/51491157