美文网首页
Android MVP设计模式总结

Android MVP设计模式总结

作者: 钟小明_ | 来源:发表于2019-04-02 19:43 被阅读0次

    MVP设计模式从提出至今也有不短的时间了,大家应该或多或少使用过MVP模式开发项目,或者至少听说过MVP设计模式,不同的人对其有不同的理解,今天就来说说我所理解的MVP设计模式。

    MVC

    说起MVP就不得不提MVC设计模式,MVP模式是从MVC模式中演化出来的。MVC包含以下三种组件:

    • 控制器(Controller)- 负责转发请求,对请求进行处理。
    • 视图(View) - 界面设计人员进行图形界面设计。
    • 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

    他们之间的联系为:

    维基百科中的MVC

    View负责显示图形界面与获取用户事件输入,事件输入之后交给Controller进行逻辑控制处理,Controller调用Model进行数据处理(如:操作数据库读取数据等),当Model中数据发生改变后会通知Controller,Controller控制View刷新界面。同时View也可以直接将数据交给Model处理,并监听Model中的数据改变来刷新界面。(详细介绍可参考维基百科中的MVC设计模式

    MVP

    MVP设计模式同样包含三种组件:

    • Model 定义用户界面所需要被显示的数据模型,一个模型包含着相关的业务逻辑。
    • View 视图为呈现用户界面的终端,用以表现来自 Model 的数据,和用户命令路由再经过 Presenter 对事件处理后的数据。
    • Presenter 包含着组件的事件处理,负责检索 Model 获取数据,和将获取的数据经过格式转换与 View 进行沟通。

    但他与MVC设计模式不同的是,MVP模式实现了View与Model的完全解耦。结构图如下:

    MVP设计模式结构图

    可以看到相比MVC模式MVP各组件之间的分工更明确,View只负责UI展示和用户事件输入,Presenter负责协调View和Model的沟通,Model负责数据操作,数据操作的结果只需要反馈给Presenter。

    这样设计的优点也显而易见:

    • 分类了视图、逻辑、数据层,降低了个模块之间的耦合性,并实现了视图层和数据层的完全解耦。
    • 个组件之间通过接口实现交互,可以很方便的进行单元测试。
    • 利于代码的复用,不同的Activity可以复用同一个Presenter,同样的不同Presenter也可以复用同一个Model进行数据处理。
    • 代码更加灵活
    • 对于大项目来说,方便不同开发人员进行模块化开发协作。

    代码实现

    上面说了这么多还得最终落实到代码上,下面将通过MVP模式实现简单的登录功能。

    效果图

    UML类图(不太熟练,如有错误,望不吝赐教):

    登录功能的UML类图

    项目结构:

    登录功能MVP模式项目结构

    分别创建View、Presenter、Model三个包存放三种组件的实现类。

    UserBean:

    用户存放用户信息的实体类,添加一个变量用于模拟不同的登录状态。

    public class UserBean {
        private String userName;
        private String password;
    
        //模拟不同的登录状态
        private String loginResultType = "1";
        private String token;
    
        public UserBean() {
        }
    
        public UserBean(String userName, String password) {
            this.userName = userName;
            this.password = password;
        }
    
        public UserBean(String userName, String password, String loginResultType, String token) {
            this.userName = userName;
            this.password = password;
            this.loginResultType = loginResultType;
            this.token = token;
        }
    
       //Getter和Setter代码不贴了
    
        @Override
        public String toString() {
            return "UserName=" + userName
                    + "\n Password=" + password
                    + "\n token=" + token;
        }
    }
    

    View:

    ILoginView接口定义:

    public interface ILoginView {
        void showLoading();
        void hideLoading();
    
        /**
         * 登录成功
         * @param userBean 用户类
         */
        void showLoginSuccess(UserBean userBean);
    
        /**
         * 显示登录失败信息
         * @param message 失败信息
         */
        void showFailureMessage(String message);
    
        /**
         * 显示登录错误信息
         * @param message 错误信息
         */
        void showErrorMessage(String message);
    }
    

    LoginActivity实现:

    public class LoginActivity extends AppCompatActivity implements ILoginView{
        private static final String TAG = LoginActivity.class.getSimpleName();
    
        private ILoginPresenter loginPresenter;         //login Presenter
    
        private RadioGroup loginResultRg;               //模拟登录状态的RadioGroup
        private EditText userNameEt;                    //用户名
        private EditText passwordEt;                    //密码
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            loginResultRg = findViewById(R.id.login_result_rg);
            userNameEt = findViewById(R.id.user_name_et);
            passwordEt = findViewById(R.id.password_et);
    
            loginPresenter = new LoginPresenter(this);
        }
    
        /**
         * 登录事件
         * @param view 事件触发View
         */
        public void login(View view) {
            UserBean userBean = new UserBean();
            userBean.setUserName(userNameEt.getText().toString().trim());
            userBean.setPassword(passwordEt.getText().toString().trim());
    
            //通过RadioButton的选中状态模拟不同的登录状态
            switch (loginResultRg.getCheckedRadioButtonId()){
                case R.id.success_rb:
                    userBean.setLoginResultType("1");
                    break;
                case R.id.failure_rb:
                    userBean.setLoginResultType("2");
                    break;
                case R.id.error_rb:
                    userBean.setLoginResultType("3");
                    break;
            }
            loginPresenter.getLoginData(userBean);
        }
    
        @Override
        public void showLoading() {
            Log.d(TAG, "showLoading");
    
            Toast.makeText(LoginActivity.this, "showLoading", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void hideLoading() {
            Log.d(TAG, "hideLoading");
            Toast.makeText(LoginActivity.this, "hideLoading", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showLoginSuccess(UserBean userBean) {
            Log.d(TAG, "showLoginSuccess user Information " + userBean.toString());
            Toast.makeText(LoginActivity.this, "showLoginSuccess userName=" + userBean.toString(), Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showFailureMessage(String message) {
            Log.d(TAG, "showFailureMessage message= " + message);
            Toast.makeText(LoginActivity.this, "showFailureMessage msg=" + message, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showErrorMessage(String message) {
            Log.d(TAG, "showErrorMessage message=" + message);
            Toast.makeText(LoginActivity.this, "showErrorMessage msg=" + message, Toast.LENGTH_SHORT).show();
        }
    }
    

    XML就不贴了需要的可以去下载demo看。

    Model:

    ILoginModel接口定义:

    public interface ILoginModel {
        /**
         * 登录操作
         * @param param 参数
         */
        void doLogin(UserBean param, LoginCallBack loginCallBack);
    
        /**
         * 登录状态回调
         */
        public interface LoginCallBack{
            /**
             * 登录成功
             * @param data 返回数据
             */
            void onSuccess(UserBean data);
    
            /**
             * 调用登录接口时,接口调用成功,但是
             *      因用户名错误、登录失效等后台控制逻辑导致的登录失败
             * @param data 失败原因
             */
            void onFailure(String data);
    
            /**
             * 接口调用失败
             *      网络不通
             *      接口超时
             *      404、500等原因
             * @param error 失败原因
             */
            void onError(String error);
    
            /**
             * 接口请求结束,包括上面三中情况
             *     设置此方法通常是进行hideLoading等操作
             */
            void onComplete();
        }
    }
    

    LoginModel:

    public class LoginModel implements ILoginModel {
        private Handler handler;
    
        public LoginModel(){
            handler = new Handler(Looper.getMainLooper());
        }
    
        @Override
        public void doLogin(final UserBean param, final LoginCallBack loginCallBack) {
            loginCallBack.onComplete();
    
            //模拟登录延迟操作
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
    
                    switch (param.getLoginResultType()){
                        case "1":
                            param.setToken("登录成功");
                            loginCallBack.onSuccess(param);
                            break;
                        case "2":
                            loginCallBack.onFailure("用户名或密码错误");
                            break;
                        case "3":
                            loginCallBack.onError("接口超时");
                            break;
                    }
                }
            }, 3000);
        }
    }
    

    Presenter:

    ILoginPresenter接口定义:

    public interface ILoginPresenter {
    
        /**
         * 获取登录数据
         * @param param 参数
         */
        void getLoginData(UserBean param);
    }
    

    LoginPresenter实现:

    public class LoginPresenter implements ILoginPresenter {
    
        private ILoginView loginView;
        private ILoginModel loginModel;
    
        public LoginPresenter(ILoginView loginView){
            this.loginView = loginView;
            loginModel = new LoginModel();
        }
    
        @Override
        public void getLoginData(UserBean userBean) {
            loginView.showLoading();
    
            loginModel.doLogin(userBean, new ILoginModel.LoginCallBack() {
                @Override
                public void onSuccess(UserBean data) {
                    loginView.showLoginSuccess(data);
                }
    
                @Override
                public void onFailure(String data) {
                    loginView.showFailureMessage(data);
                }
    
                @Override
                public void onError(String error) {
                    loginView.showErrorMessage(error);
                }
    
                @Override
                public void onComplete() {
                    loginView.hideLoading();
                }
            });
        }
    }
    

    一句话总结一下登录流程:
    用户点击登录按钮触发登录操作,View也就是LoginActivity调用Presenter的getLoginData()方法,开启登录逻辑,Presenter调用Model的doLogin方法,执行具体的登录操作。Model将登录结果通过回调反馈给Presenter,Presenter控制View进行相应的UI显示。

    另一种实现:

    上面这种实现是最基本的实现,下面介绍另一种实现,将IView、IModel、IPresenter中的接口封装到contract中,并实现相关的基类方便其他模块扩展实现MVP模式。

    UML类图:

    MVP类图

    项目结构

    MVP项目结构图

    Contract类

    添加了Contract包,用于存放不同模块的协约类,用于将上一种实现方式中分散在IView、IModel、IPresenter中的接口统一归纳、统一管理。

    public class LoginContract {
        /**
         * 登录View接口
         */
        public interface ILoginView {
            void showLoading();
            void hideLoading();
    
            /**
             * 登录成功
             * @param userBean 用户类
             */
            void showLoginSuccess(UserBean userBean);
    
            /**
             * 显示登录失败信息
             * @param message 失败信息
             */
            void showFailureMessage(String message);
    
            /**
             * 显示登录错误信息
             * @param message 错误信息
             */
            void showErrorMessage(String message);
        }
    
        /**
         * 登录Presenter
         */
        public interface ILoginPresenter {
    
            /**
             * 获取登录数据
             * @param param 参数
             */
            void getLoginData(UserBean param);
        }
    
        /**
         * 登录Model
         */
        public interface ILoginModel {
            /**
             * 登录操作
             * @param param 参数
             */
            void doLogin(UserBean param, LoginCallBack loginCallBack);
        }
    }
    

    View

    IView:
    定义了一个IView接口类,此类中抽象出所有View共同的方法,如:showLoading、hideLoading等,还有个作用就是为所有的View定义统一的接口方便之后在BasePresenter中进行泛型。

    public interface IView {
        //定义统一的空接口
    }
    

    BaseActivity:
    定义基类BaseActivity,封装一些通用方法便于其他模块的Activity进行扩展。注意在此类中实现了IView接口,所以在之后的Activity中不在需要实现IView接口。

    public abstract class BaseActivity<P extends IPresenter> extends AppCompatActivity implements IView{
        //定义Presenter的泛型进行约束
        protected P mPresenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            if(initLayout() instanceof Integer){
                setContentView((Integer) initLayout());
            } else if(initLayout() instanceof View){
                setContentView((View) initLayout());
            } else{
                throw new IllegalArgumentException("initLayout() 应该返回Int或者View类型对象");
            }
    
            //初始化Presenter
            mPresenter = initPresenter();
            //Presenter与View进行绑定
            mPresenter.attachView(this);
    
            create();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //Presenter与View解除绑定
            mPresenter.detachView();
        }
    
        /** 初始化Presenter的抽象方法 */
        protected abstract P initPresenter();
        /** 初始化布局的抽象方法 */
        protected abstract Object initLayout();
        /** Activity OnCreate之后的create抽象方法 */
        protected abstract void create();
    }
    

    LoginActivity:
    LoginActivity需要继承其父类BaseActivity并实现Login协约类中的View接口。

    ublic class LoginActivity extends BaseActivity<LoginPresenter> implements LoginContract.ILoginView {
        private static final String TAG = LoginActivity.class.getSimpleName();
    
        private RadioGroup loginResultRg;               //模拟登录状态的RadioGroup
        private EditText userNameEt;                    //用户名
        private EditText passwordEt;                    //密码
    
        @Override
        protected LoginPresenter initPresenter() {
            return new LoginPresenter();
        }
    
        @Override
        protected Object initLayout() {
            return R.layout.activity_main;
        }
    
        @Override
        protected void create() {
            loginResultRg = findViewById(R.id.login_result_rg);
            userNameEt = findViewById(R.id.user_name_et);
            passwordEt = findViewById(R.id.password_et);
        }
    
        /**
         * 登录事件
         * @param view 事件触发View
         */
        public void login(View view) {
            UserBean userBean = new UserBean();
            userBean.setUserName(userNameEt.getText().toString().trim());
            userBean.setPassword(passwordEt.getText().toString().trim());
    
            //通过RadioButton的选中状态模拟不同的登录状态
            switch (loginResultRg.getCheckedRadioButtonId()){
                case R.id.success_rb:
                    userBean.setLoginResultType("1");
                    break;
                case R.id.failure_rb:
                    userBean.setLoginResultType("2");
                    break;
                case R.id.error_rb:
                    userBean.setLoginResultType("3");
                    break;
            }
    
            mPresenter.getLoginData(userBean);
        }
    
        @Override
        public void showLoading() {
            Log.d(TAG, "showLoading");
    
            Toast.makeText(LoginActivity.this, "showLoading", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void hideLoading() {
            Log.d(TAG, "hideLoading");
            Toast.makeText(LoginActivity.this, "hideLoading", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showLoginSuccess(UserBean userBean) {
            Log.d(TAG, "showLoginSuccess user Information " + userBean.toString());
            Toast.makeText(LoginActivity.this, "showLoginSuccess userName=" + userBean.toString(), Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showFailureMessage(String message) {
            Log.d(TAG, "showFailureMessage message= " + message);
            Toast.makeText(LoginActivity.this, "showFailureMessage msg=" + message, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showErrorMessage(String message) {
            Log.d(TAG, "showErrorMessage message=" + message);
            Toast.makeText(LoginActivity.this, "showErrorMessage msg=" + message, Toast.LENGTH_SHORT).show();
        }
    }
    

    Model

    IModel:
    统一的接口类IModel,作用同上IView接口。

    public interface IModel {
    
    }
    

    LoginModel:
    实现IModel和LoginContract.ILoginModel接口

    public class LoginModel implements IModel,LoginContract.ILoginModel {
        private Handler handler;
    
        public LoginModel(){
            handler = new Handler(Looper.getMainLooper());
        }
    
        @Override
        public void doLogin(final UserBean param, final LoginCallBack loginCallBack) {
            loginCallBack.onComplete();
    
            //模拟登录延迟操作
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
    
                    switch (param.getLoginResultType()){
                        case "1":
                            param.setToken("登录成功");
                            loginCallBack.onSuccess(param);
                            break;
                        case "2":
                            loginCallBack.onFailure("用户名或密码错误");
                            break;
                        case "3":
                            loginCallBack.onError("接口超时");
                            break;
                    }
                }
            }, 3000);
        }
    }
    

    Presenter

    IPresenter:
    统一的Presenter接口,定义了绑定和解绑View的方法。

    public interface IPresenter {
        void attachView(IView view);
        void detachView();
    }
    

    BasePresenter:
    定义View与Model的泛型进行约束,实现上面的接口。

    public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter{
    
        protected V mView;
        protected M mModel;
    
        public BasePresenter(){
            mModel = initModel();
        }
    
        @Override
        public void attachView(IView view) {
            mView = (V) view;
        }
        
        /**
        *初始化Moel的抽象方法
        */
        protected abstract M initModel();
    
        @Override
        public void detachView() {
            mView = null;
            mModel = null;
        }
    }
    

    LoginPresenter:
    继承基类,实现接口,没什么好说的。

    public class LoginPresenter extends BasePresenter<LoginActivity, LoginModel> implements LoginContract.ILoginPresenter {
    
        @Override
        public void getLoginData(UserBean userBean) {
            mView.showLoading();
    
            mModel.doLogin(userBean, new LoginContract.ILoginModel.LoginCallBack() {
                @Override
                public void onSuccess(UserBean data) {
                    mView.showLoginSuccess(data);
                }
    
                @Override
                public void onFailure(String data) {
                    mView.showFailureMessage(data);
                }
    
                @Override
                public void onError(String error) {
                    mView.showErrorMessage(error);
                }
    
                @Override
                public void onComplete() {
                    mView.hideLoading();
                }
            });
        }
    
        @Override
        protected LoginModel initModel() {
            return new LoginModel();
        }
    }
    

    至此关于MVP就介绍完了,并扩展了一种MVP的实现方式,实现方式并不是固定的,你可以根据自己对MVP的理解和项目需要自行实现MVP设计模式。

    Demo:博客中的项目Demo

    [1]文中引用部分均来自中文维基百科]1

    相关文章

      网友评论

          本文标题:Android MVP设计模式总结

          本文链接:https://www.haomeiwen.com/subject/xvjnbqtx.html