一、 什么是MVP
MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
二、MVP与MVC的区别
作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。
三、Android中的MVP
代码案例:Login MVP
- Model
/** 模型层 ——— 登录接口 **/
public interface ILoginModel {
void login(String username, String password, LoginCallBack callBack);
}
/** 模型层 ——— 完成具体的数据操作。 **/
public class LoginModel implements ILoginModel {
@Override
public void login(String username, String password, LoginCallBack callBack) {
if (username.equals("MVP") && password.equals("MVP")){
callBack.onLoginSuccess();
}else{
callBack.onLoginFail();
}
}
}
- View
/** 视图层 ——— 视图操作接口 **/
public interface ILoginView {
void initView();
void onUsernameEmpty(); // 用户名为空时的显示操作
void onPasswordEmpty(); // 密码为空时的显示操作
void onLoginSuccess(); // 登陆成功时的显示操作
void onLoginFail(); // 密码失败时的显示操作
}
/** 视图层 ——— 只是作为接受用户数据和展示数据的方式 **/
public class LoginView extends LinearLayout implements ILoginView, View.OnClickListener{
private Context context;
private EditText mUsername;
private EditText mPassword;
private Button mLoginBtn;
private ILoginPresenter presenter;
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
public void initView(){
mUsername = (EditText) findViewById(R.id.et_username);
mPassword = (EditText) findViewById(R.id.et_password);
mLoginBtn = (Button) findViewById(R.id.btn_login);
presenter = new LoginPresenter(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login:
presenter.login(mUsername.getText().toString(),
mPassword.getText().toString());
break;
}
}
@Override
public void onUsernameEmpty() {
Toast.makeText(context, "用户名不能为空", Toast.LENGTH_SHORT).show();
}
@Override
public void onPasswordEmpty() {
Toast.makeText(context, "密码不能为空", Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginSuccess() {
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onLoginFail() {
Toast.makeText(context, "登录失败", Toast.LENGTH_SHORT).show();
}
}
- Presenter
public interface ILoginPresenter {
void login(String username, String password);
}
public class LoginPresenter implements ILoginPresenter, LoginCallBack{
private ILoginModel loginModel;
private ILoginView loginView;
public LoginPresenter(ILoginView loginView){
loginModel = new LoginModel();
this.loginView = loginView;
}
@Override
public void login(String username, String password) {
if (TextUtils.isEmpty(username)){
loginView.onUsernameEmpty();
}else if (TextUtils.isEmpty(password)){
loginView.onPasswordEmpty();
}else {
loginModel.login(username, password, this);
}
}
@Override
public void onLoginSuccess() {
loginView.onLoginSuccess();
}
@Override
public void onLoginFail() {
loginView.onLoginFail();
}
}
- else
/** 此时Activity就变为了承载视图层的容器。 **/
public class MainActivity extends AppCompatActivity {
private LoginView loginview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initPresenter();
}
private void initPresenter(){
loginview = (LoginView) findViewById(R.id.loginview);
loginview.initView();
}
}
<!-- 将LoginView作为父容器 -->
<com.scnu.zhou.mvc.view.LoginView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:id="@+id/loginview">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"/>
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:inputType="textPassword"/>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Login"
android:layout_marginTop="10dp"/>
</com.scnu.zhou.mvc.view.LoginView>
三、 MVP模式的优缺点
优点:
1、模型与视图完全分离,我们可以修改视图而不影响模型
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)
缺点:
由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。还有一点需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。比如说,原本用来呈现Html的Presenter现在也需要用于呈现Pdf了,那么视图很有可能也需要变更。