浅谈Android中的MVC与MVP模式

时间:2021-09-08 16:52:31

使用MVC或者MVP模式会增加很多的类,但是确可以让代码结构变得清晰,方便了后期维护拓展方便。把数据层跟视图层分离,处理事务的逻辑单独的放在一个类中,让Activity仅仅具有展示功能。

下面我们就MVC模式跟MVP模式进行分别讲解,总之来说各有利弊。在实际的开发中,我们根据实际情况进行取舍。个人认为MVP模式更简单一些,因为MVP模式中会把部分逻辑Activity中,但是这就造成了Activity的相对繁琐,没有实现完全的隔离。而我们采用的MVC模式则是更好的处理了这个问题,但是在应用的过程中,部分逻辑可能比较绕。

下面我们通过一个用户登录这个例子来分别理解这两种模式。

首先来讲解MVC模式:

这里说将对MVC模式不是传统的MVC模式,在使用Fragment的嵌套到Activity中的时候,其实就是进行碎片化,把逻辑视图全部交给Fragment,然而不幸的是Fragment的生命周期过于繁琐,导致在使用的过程中会遇到各种各样的坑,让人头疼。Square公司也发现了这个问题,于是有了本文所讲的MVC模式,当你有了足够的编程经验,这套体系模式就会自动在你脑海中生成。具体的实现思路是将model层,view层,controller层分离,而Activity只是用来加载视图使用,初始化控件交给view层,对数据的获取交给model层,对数据的处理交给controller层,我们的Activity是异常的清爽!

首先我们来设计view层,自定义一个view,继承自我们需要的viewgroup,LinearLayout或者FrameLayout等。自定义的目的是为了下一步实例化控件,从而不必在Activity中实例化了。这里我们只是简单的继承,通常不需要自己多加逻辑,所以不要慌。代码如下:

public class LoginView extends RelativeLayout{

    private Context mContext;

    /**
     * 因为我们是在布局文件中使用,所以只需要重写这个构造方法就可以了
     * @param context
     * @param attrs
     */
    public LoginView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
    }

}


现在我们就使用已经定义好的 viewgroup来进行布局,只是一个简单的登录窗口,一个用户名,一个密码,还有一个登录按键:

<?xml version="1.0" encoding="utf-8"?>
<com.baiyyyhjl.mode.mvc.view.LoginView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"
        android:padding="10dp" />

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/et_username"
        android:hint="密码"
        android:padding="10dp" />

    <Button
        android:id="@+id/btn_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/et_password"
        android:padding="10dp"
        android:text="登录" />
</com.baiyyyhjl.mode.mvc.view.LoginView>


现在我们在LoginView中添加控件的初始化的方法。

public void initModule(){
        mUsername = (EditText) findViewById(R.id.et_username);
        mPassword = (EditText) findViewById(R.id.et_password);
        mLoginBtn = (Button) findViewById(R.id.btn_login);
    }


获取到EditText中的内容,以及对mLoginBtn点击事件的监听,这里我们可以添加更多关于view的操作,一切根据实际需求来:

    public String getUserName(){
        return mUsername.getText().toString().trim();
    }

    public String getPassword(){
        return mPassword.getText().toString().trim();
    }

    public void setListeners(OnClickListener onClickListener){
        mLoginBtn.setOnClickListener(onClickListener);
    }

    public void userNameError(Context context){
        Toast.makeText(context, "用户名不能为空", Toast.LENGTH_SHORT).show();
    }

    public void passWordError(Context context){
        Toast.makeText(context, "密码不能为空", Toast.LENGTH_SHORT).show();
    }

    public void loginSuccess(Context context){
        Toast.makeText(context, "登录成功啦!", Toast.LENGTH_SHORT).show();
    }
    public void loginFailure(Context context){
        Toast.makeText(context, "登录失败。", Toast.LENGTH_SHORT).show();
    }
}


好了,一个view层已经完成,下面来说model层,主要是对数据的请求,比如获取数据库中的数据,网络请求等:

这里我们假定进行网路请求,一个LoginModel,有一个返回结果的回调接口,具体模拟代码如下:

<pre style="font-family: 宋体; font-size: 15pt; background-color: rgb(255, 255, 255);"><pre name="code" class="java">public class LoginModel implements ILoginModel {
    @Override
    public void login(final String username, final String password, final ResultCallBack callBack) {
        // 模拟子线程的耗时操作,例如网路请求,这里我们使用异步请求
        new MyAsyncTask(callBack).execute(username, password);
    }

    private class MyAsyncTask extends AsyncTask<String, Void, Boolean>{

        ResultCallBack mCallBack;

        public MyAsyncTask(ResultCallBack callBack){
            this.mCallBack = callBack;
        }

        @Override
        protected Boolean doInBackground(String... params) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (params[0].equals("hjl") && params[1].equals("123456")){
                return true;
            } else {
                return false;
            }
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            if (aBoolean){
                mCallBack.success();
            } else {
                mCallBack.failure();
            }
        }
    }
}


 
 

最后实现controller层:

public class LoginController implements View.OnClickListener{

    private LoginView mLoginView;
    private MVCLoginActivity mContext;
    private ILoginModel mLoginModel;

    public LoginController(LoginView loginView, MVCLoginActivity context){
        this.mLoginView = loginView;
        this.mContext = context;
        mLoginModel = new LoginModel();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_login:
                final String username = mLoginView.getUserName();
                final String password = mLoginView.getPassword();
                if (TextUtils.isEmpty(username)){
                    mLoginView.userNameError(mContext);
                    break;
                }
                if (TextUtils.isEmpty(password)){
                    mLoginView.passWordError(mContext);
                    break;
                }
                // 调用model层进行网络请求
                mLoginModel.login(username, password, new ResultCallBack() {
                    @Override
                    public void success() {
                        mLoginView.loginSuccess(mContext);
                        mContext.finish();
                    }

                    @Override
                    public void failure() {
                        mLoginView.loginFailure(mContext);
                    }
                });
                break;
        }
    }
}

这样MVC框架就完成了,在Activity中展示:

public class MVCLoginActivity extends AppCompatActivity {

    private LoginView mLoginView = null;
    private LoginController mLoginController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mvc_activity_login);
        // 控件的绑定
        mLoginView = (LoginView) findViewById(R.id.login_view);
        mLoginView.initModule();
        mLoginController = new LoginController(mLoginView, this);
        // 事件的监听
        mLoginView.setListeners(mLoginController);
    }

}

一个相对复杂的登录操作,在我们的Activity中只有简单的几行代码,具体的逻辑全都交给MVC,这里我们的思想是Activity不属于view层,而只供展示以及一些简单逻辑的处理,我们把MVC层全部单独为几个类,让Activity中仅仅几行代码,而且整体的代码结构相当的清晰!总结:对于控件的绑定等相关操作逻辑我们写在view层中,对于数据的获取(读取数据库,获取网络数据)我们写在model中,对于代码的所有逻辑(点击事件的处理,相关事件)等我们写在controller层中。

下一项我们来介绍MVP模式,个人感觉这个模式更好理解,弊端在文章开头已经说了。

之前看过大神分析过MVP模式,其实我第一次接触这种模式是我刚工作不久,公司项目里用到。当时因为基础不大好,总感觉超级麻烦,又是这么多类,这么多接口的,直接写在Activity中多省事。后来代码写的多一点了,就发现如果全都写在Activity中维护起来相当困难,有时候代码量多了,可能自己都看不懂自己写的代码了,也渐渐理解了设计模式的好处。


我们还是以这个登录需求为例,用MVP模式写一遍,体会一下。model层还是进行实体模型和业务逻辑,view层这里我们直接使用Activity,就是绑定控件等操作放在Activity中,不再多加一个类,presenter层负责处理model和view之间的交互。

首先还是写view层接口ILoginView,这里我们需要把所有需要的条件考虑到:

public interface ILoginView {
    String getUsername();
    String getPassword();
    void userError(Context context);
    void passwordError(Context context);
    void loginSuccess(Context context);
    void loginFailure(Context context);
}


接下来是model层,模拟网络请求

public class LoginModel implements ILoginModel {
    @Override
    public void login(final String username, final String password, final ResultCallBack callBack) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (username.equals("hjl") && password.equals("123456")) {
                    callBack.success();
                } else {
                    callBack.failure();
                }
            }
        }).start();
    }
}


最后在Activity中实现:

public class MVPLoginActivity extends AppCompatActivity implements View.OnClickListener, ILoginView {

    private EditText mUsername;
    private EditText mPassword;
    private Button mLoginBtn;
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mvc_activity_login);
        mLoginPresenter = new LoginPresenter(this);
        initView();
    }

    private void initView() {
        mUsername = (EditText) findViewById(R.id.et_username);
        mPassword = (EditText) findViewById(R.id.et_password);
        mLoginBtn = (Button) findViewById(R.id.btn_login);
        mLoginBtn.setOnClickListener(this);
    }

    @Override
    public String getUsername() {
        return mUsername.getText().toString().trim();
    }

    @Override
    public String getPassword() {
        return mPassword.getText().toString().trim();
    }

    @Override
    public void loginSuccess() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
        finish();
    }

    @Override
    public void loginFailure() {
        Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:
                if (TextUtils.isEmpty(getUsername())) {
                    Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show();
                    break;
                }
                if (TextUtils.isEmpty(getPassword())) {
                    Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
                    break;
                }
                mLoginPresenter.login();
                break;
        }
    }
}


以上就是android中的两种设计模式,不知道自己写的是否标准,但是感觉这样写已经达到了代码结构的清晰。其中MVC是借鉴极光推送im的demo,从中学习,而且感觉他写的这种方法是在很棒。第二种是项目中用到的,也很简单清晰。

以上所说的demo下载点击打开链接