前面在设计模式(1):andorid中的MVC模式,我们讲到了MVC模式的问题,所以自然引出了我们今天的主角:MVP模式
MVP优点:
- 分离了视图逻辑和业务逻辑,降低了耦合
- Activity 只处理生命周期的任务,代码变得更加简洁
- 视图逻辑和业务逻辑分别抽象到了 View 和 Presenter 的接口中去,提高代码的可阅读性
- Presenter 被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
- 把业务逻辑抽到 Presenter 中去,避免后台线程引用着 Activity 导致 Activity 的资源无法被系统回收从而引起内存泄露和 OOM
其中说的方便单元测试指的就是:
一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做 MV 模式,少了 P),我们可能要先在 Activity 里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……
MVP 中,由于业务逻辑都在 Presenter 里,我们完全可以写一个 PresenterTest 的实现类继承 Presenter 的接口,现在只要在 Activity 里把 Presenter 的创建换成 PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成 PresenterTest 吧。
MVP 的主要特点就是把 Activity 里的许多逻辑都抽离到 View 和 Presenter 接口中去,并由具体的实现类来完成。这种写法多了许多 IView 和 IPresenter 的接口,在某种程度上加大了开发的工作量,刚开始使用 MVP 的小伙伴可能会觉得这种写法比较别扭,而且难以记住。其实一开始想太多也没有什么卵用,只要在具体项目中多写几次,就能熟悉 MVP 模式的写法,理解 TA 的意图,以及享♂受其带来的好处。
MVP使用实例:
一个简单的登录界面(实在想不到别的了╮( ̄▽ ̄”)╭),点击 LOGIN 则进行账号密码验证,点击 CLEAR 则重置输入。
首先来看看 LoginActivity:
public class LoginActivity extends ActionBarActivity implements ILoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
ILoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//find view
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
//set listener
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//init
loginPresenter = new LoginPresenterCompl(this);
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.clear();
break;
case R.id.btn_login_login:
loginPresenter.setProgressBarVisiblity(View.VISIBLE);
btnLogin.setEnabled(false);
btnClear.setEnabled(false);
loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
btnLogin.setEnabled(true);
btnClear.setEnabled(true);
if (result){
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, HomeActivity.class));
}
else
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
@Override
public void onSetProgressBarVisibility(int visibility) {
progressBar.setVisibility(visibility);
}
}
从代码可以看出 LoginActivity 只做了 findView 以及 setListener 的工作,而且包含了一个 ILoginPresenter,所有业务逻辑都是通过调用 ILoginPresenter 的具体接口来完成。所以 LoginActivity 的代码看起来很舒爽,甚至有点愉♂悦呢 (/ω\*)。视力不错的你可能还看到了 ILoginView 接口的实现,如果不懂为什么要这样写的话,可以先往下看,这里只要记住 “LoginActivity 实现了 ILoginView 接口”。
再来看看 ILoginPresenter
public interface ILoginPresenter {
void clear();
void doLogin(String name, String passwd);
void setProgressBarVisiblity(int visiblity);
}
public class LoginPresenterCompl implements ILoginPresenter {
ILoginView iLoginView;
IUser user;
Handler handler;
public LoginPresenterCompl(ILoginView iLoginView) {
this.iLoginView = iLoginView;
initUser();
handler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
iLoginView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
Boolean isLoginSuccess = true;
final int code = user.checkUserValidity(name,passwd);
if (code!=0) isLoginSuccess = false;
final Boolean result = isLoginSuccess;
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginView.onLoginResult(result, code);
}
}, 3000);
}
@Override
public void setProgressBarVisiblity(int visiblity){
iLoginView.onSetProgressBarVisibility(visiblity);
}
private void initUser(){
user = new UserModel("mvp","mvp");
}
}
从代码可以看出,LoginPresenterCompl 保留了 ILoginView 的引用,因此在 LoginPresenterCompl 里就可以直接进行 UI 操作了,而不用在 Activity 里完成。这里使用了 ILoginView 引用,而不是直接使用 Activity,这样一来,如果在别的 Activity 里也需要用到相同的业务逻辑,就可以直接复用 LoginPresenterCompl 类了(一个 Activity 可以包含一个以上的 Presenter,总之,需要什么业务就 new 什么样的 Presenter,是不是很灵活(@ ̄︶ ̄@)),这也是 MVP 的核心思想
再来看看 ILoginView,至于 ILoginView 的实现类呢,翻到上面看看 LoginActivity 吧
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
上面的内容来自我引用的个人IT站,我自己在这里总结一下:
无一例外,M(Model)V(View)P(Prenster)三者首先都要有一个interface接口,里面写着你根据业务逻辑要求想要实现的抽象方法。然后这些抽象方法最后会在对应的实现类里实现。
先说V(View):
很显然,Activity是作为View的具体实现类用来处理界面逻辑,LoginActivity继承ILoginView,在ILoginView中我们定义好我们界面逻辑需要的抽象方法,然后在LoginActivity中实现,这个没有太多可说的再说P(Presenter):
P处理业务逻辑,一个接口:ILoginPresenter,然后在里面写入我们需要的业务逻辑抽象方法,再创建一个类LoginPresenterCompl ,继承 ILoginPresenter,然后实现这些继承的抽象方法。
这里有一个小问题,就是业务逻辑处理完之后往往需要对界面作出调整,所以我们需要在LoginPresenterCompl 创建一个ILoginView 引用,然后我们在Activity中创建LoginPresenterCompl 对象的时候把Activity本身当作参数传入(注意这个Activity是继承了ILoginView 的)赋值给这个ILoginView 引用,然后根据多态性,我们就可以直接在LoginPresenterCompl 中对这个ILoginView 进行操作(调用的实际上却是Activity中实现了的ILoginView 方法)。关于M(Model):
这个没什么可说的,就不说了
当然以上只是最简单的MVP模式的运用,还有一些我们常用的比如说Adapter的MVP模式使用我们以后再更新。