Android开发部落Android知识Android 技术开发

最简单的 MVP 理解

2017-07-08  本文已影响311人  imyyq_star

前言

一个好的软件总是离不开好的架构,不管是前端后端。
在Android中,我已知的设计模式有:MVC,MVP、MVVM、Clean,其中各自的优劣不再这里展开,有需要的自行Google。
这里探讨一下MVP,在很多的文章中,都讲很多的概念性的东西,时常把人讲的云里雾里,对于刚接触的人,就算是理解了,怎么实际应用都不知道。
因此本文就用最简单最常见的来介绍MVP,其实架构是一种很活的东西,谁说你必须使用某种模式?谁规定代码一定要这么写才是对的?我认为只有在变化中能不断适应的,才是王道。难道后来你会了另一种模式,就不能在已有的项目中应用了吗?
我认为只要是你逻辑清晰,分层合理,你想怎么玩都行,甚至不用任何所谓的模式,注意:前提是分层一定要清晰,层与层之间的界限要清晰明了。

先不管概念,来一段简单的代码先

需求:用户输入账号密码,点击登录按钮进行登录。

代码如下:注意,只是作为示范用。有所删减,看得懂意图就好。

activity_login.xml:
如图,xml 的代码就不贴了,很简单。

LoginActivity.java:

public class LoginActivity
        extends AppCompatActivity
{
    private EditText etAccount;
    private EditText etPwd;

    @Override
    protected void onCreate(
            @Nullable
                    Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        etAccount = (EditText) findViewById(R.id.et_account);
        etPwd = (EditText) findViewById(R.id.et_pwd);
    }

    // 响应登录按钮
    public void onLogin(View view)
    {
        String account = etAccount.getText().toString();
        String pwd = etPwd.getText().toString();

        // TODO 这里省掉了空判断

        // 发起请求
        RequestParams params = new RequestParams();
        params.add("account", account);
        params.add("pwd", pwd);
        new AsyncHttpClient().get("url", params, new Login());
    }

    // 登录请求回调
    private class Login
            extends AsyncHttpResponseHandler
    {
        @Override
        public void onSuccess(int statusCode, Header[] headers, byte[] responseBody)
        {
            if (responseBody != null)
            {
                LoginResponse response = JSON.parseObject(new String(responseBody),
                        LoginResponse.class);
                if (response != null)
                {
                    if (response.getStatus() == 0)
                    {
                        Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();

                        // TODO 去到主界面之类的

                        // 然后结束掉登录
                        finish();
                    }
                    else
                    {
                        Toast.makeText(LoginActivity.this, "登录失败," + response.getMsg(),
                                Toast.LENGTH_SHORT).show();
                    }
                }
            }
            else
            {
                onFailure(statusCode, headers, null, null);
            }
        }

        @Override
        public void onFailure(int statusCode, Header[] headers, byte[] responseBody,
                              Throwable error)
        {
            Toast.makeText(LoginActivity.this, "登录失败,请检查网络", Toast.LENGTH_SHORT).show();
        }
    }
}

很简单吧?就是输入和发起登录。
其中网络库使用的是:
android-async-http
JSON解析使用的是:
FastJSON Android版本

分析,以上代码一共分为多少层?有什么缺陷?

从分析来看,上述一个简单的需求实际上有三个层的存在,而却全部写在View中,对于新手来说,这样类似的代码是再正常不过了。
一般来说,简单的需求,项目小,这样写也不会造成什么问题的,但是一旦项目越来越大,并且需求改动也越来越多的时候,就成了一种灾难了,比如无休止的复制和粘贴。

举个例子:
现在项目中加入了启动页,要求在启动页判断先前是否已有用户登录过,如果有,则取出账号密码进行登录,登录成功去到主界面,失败则去到登录页;
如果没有,直接跳转到登录页。

再用上面的写法,也就是加个启动页,然后复制登录的那段代码,再改改回调处理的,听起来好像没事,但不觉得重复了吗?

使用MVP模式重写

先看一张类图


你肯定会说:什么?一个简单的功能,居然需要这么多类文件,这不是更加烦琐,工作量更加大了吗?
别急,继续看。下面我们就按上面分析的来写。

LoginResponse.java :JSON 解析需要的数据类

public class LoginResponse
{
    private int status;
    private String msg;

    ...省略掉 set/get
}

再来看两个 Base 类:

BasePresenter.java:

/**
 * 所有Presenter的父接口
 */
public interface BasePresenter
{
    // TODO 在这里可以声明一些Presenter的通用方法
}

BaseView.java:

/**
 * 所有View的父接口
 */
public interface BaseView
{
    // TODO 在这里可以声明一些View的通用方法
}

不知道定义两个 Base 是用来干嘛的,没关系,再来思考关于这个登录界面的两个问题:

针对以上问题,解答如下:

因此我们可以�把这些操作和显示都归类到一个地方,称为契约类(Contract)

LoginContract.java:

/**
 * 登录契约类,声明了View和Presenter该有的操作,方便管理
 */
public interface LoginContract
{
    // 定义界面中所有的 UI 状态
    interface View
            extends BaseView
    {
        void loginSuccess(); // 登录成功

        void loginFailure(String msg); // 登录失败

        void showLoading(boolean isShowLoading); // 是否显示加载中
    }

    // 定义了所有的用户操作
    interface Presenter
            extends BasePresenter
    {
        void login(String account, String pwd); // 登录
    }
}

定义契约类的目的是方便管理,也能理清你的逻辑。

好了,�以上都是准备工作,实际的 Model、View、Presenter 相关的具体类还没写。继续看。

Model:LoginRequest.java

public final class LoginRequest
{
    // 单例
    private LoginRequest()
    {
    }

    private static class SingletonHolder
    {
        private static final LoginRequest SINGLETON = new LoginRequest();
    }

    public static LoginRequest getInstance()
    {
        return SingletonHolder.SINGLETON;
    }

    public void login(String account, String pwd, final LoginCallback callback)
    {
        // 发起请求
        RequestParams params = new RequestParams();
        params.add("account", account);
        params.add("pwd", pwd);
        new AsyncHttpClient().get("url", params, new AsyncHttpResponseHandler()
        {
            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody)
            {
                callback.onSuccess(statusCode, responseBody);
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] responseBody,
                                  Throwable error)
            {
                callback.onFailure(statusCode, responseBody, error);
            }
        });
    }

    // 对外暴露的接口
    public interface LoginCallback
    {
        void onFailure(int statusCode, byte[] responseBody, Throwable error);

        void onSuccess(int statusCode, byte[] responseBody);
    }
}

Model 类不负责逻辑的处理,只是负责增删改查,以及必要的保存住自己的状态,比如你这个 Model 表示一个智能开关设备,那么开关的状态你得保存起来,以便�状态改变的时候发出通知,以及外面的人来你这拿状态的时候,你得给人家正确的状态。
这里的 Model 表示登录,login 方法被调用后,将登录结果通过 LoginCallback 回传给调用者就完成了职责。

再来看 View。

View:�LoginActivity.java:

public class LoginActivity
        extends AppCompatActivity
        implements LoginContract.View // 实现了�契约类中的接口
{
    private EditText etAccount;
    private EditText etPwd;

    private LoginContract.Presenter loginPresenter;

    @Override
    protected void onCreate(
            @Nullable
                    Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        // 创建Presenter,使View和Presenter,Presenter和Model关联起来,这一步暂且忽略也可以,等看到 Presenter 了再回来看。
        loginPresenter = new LoginPresenter(this, LoginRequest.getInstance());

        etAccount = (EditText) findViewById(R.id.et_account);
        etPwd = (EditText) findViewById(R.id.et_pwd);
    }

    public void onLogin(View view)
    {
        String account = etAccount.getText().toString();
        String pwd = etPwd.getText().toString();

        // 告知Presenter发起登录
        loginPresenter.login(account, pwd);
    }

    @Override
    public void showLoading(boolean isShowLoading)
    {
        // 显示和隐藏进度条
    }

    @Override
    public void loginSuccess()
    {
        /*
        比如取消进度条,进入到主页面
         */
    }

    @Override
    public void loginFailure(String msg)
    {
        /*
        取消进度条,显示登录错误提示,比如密码错误、账号不存在之类的
         */
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

可以看到,View 中没有任何的逻辑处理和数据获取,能做的��只是跟界面相关的操作,向�外界发出请求,以及�暴露给外界操作界面的方法。注意:View 中不能有任何的业务逻辑处理,只能有和 View 相关的操作。

可以看到,Model 和 View 是完全分开的,没有任何的直接关联。下一步,我们需要通过 Presenter 将他们关联起来。

Presenter:LoginPresenter.java

public class LoginPresenter
        implements LoginContract.Presenter
{
    // Presenter �持有 View 和 Model 的引用
    private LoginContract.View loginView;
    private LoginRequest loginRequest;

    public LoginPresenter(LoginContract.View loginView, LoginRequest loginRequest)
    {
        this.loginView = loginView;
        this.loginRequest = loginRequest;
    }

    @Override
    public void login(String account, String pwd)
    {
        // 账号密码不对的话,直接失败
        if (TextUtils.isEmpty(account.trim()) || TextUtils.isEmpty(pwd))
        {
            loginView.showLoading(false);
            loginView.loginFailure("账号密码不对");
            return;
        }

        loginView.showLoading(true);

        loginRequest.login(account, pwd, new LoginRequest.LoginCallback()
        {
            @Override
            public void onFailure(int statusCode, byte[] responseBody, Throwable error)
            {
                loginView.loginFailure("登录错误的提示信息");
            }

            @Override
            public void onSuccess(int statusCode, byte[] responseBody)
            {
                if (responseBody != null)
                {
                    LoginResponse response = JSON.parseObject(new String(responseBody),
                            LoginResponse.class);
                    if (response != null)
                    {
                        if (response.getStatus() == 0)
                        {
                            loginView.loginSuccess();
                        }
                        else
                        {
                            loginView.loginFailure("登录错误的提示信息");
                        }
                    }
                    else
                    {
                        loginView.loginFailure("登录错误的提示信息");
                    }
                }
                else
                {
                    loginView.loginFailure("登录错误的提示信息");
                }
            }
        });
    }
}

可以看到,所有的业务逻辑都在 Presenter 里面了。

看看以上的代码是不是符合下面这张图:

Model 和 View 是完全分离的,以上通过小实例目的是为了让大家理解并用起来,更复杂彻底的 MVP ,可以查看 Google 的官方 Sample:
googlesamples/android-architecture

还有一个开源项目:
android10/Android-CleanArchitecture

MVP 的好处

MVP 的坏处

结语:

本文的目的是让没有玩过 MVP 的设计快速入门的,�理解了以上内容,�进阶的内容可自己 Google,很多这方面的资料。

上一篇下一篇

猜你喜欢

热点阅读