Android中MVC架构和MVP架构的实践详解 通俗易懂的Demo

版权声明:本文为博主原创文章,转载请标明出处 https://blog.csdn.net/qq_30993595/article/details/83185033

前言

MVC代码下载地址MVCDemo
MVP代码下载地址MVPDemo

相信从事软件开发的伙计们肯定熟悉或者听说过项目架构,比如要新开发一个APP或者Web项目,首先考虑的就是项目需要设计什么样的架构,MVC还是MVP呢?MVC和MVP具体是怎么体现的,有哪些优点,哪些缺点呢?

为什么需要架构设计

假如我们不需要架构设计,那我们的APP项目代码会写成什么样,会不会Activity里有加载View,初始化View的代码;
如果需要跟服务器的交流,那也会有网络操作代码;如果有需要从本地数据库存取数据,那么也会有数据库操作代码;
如果有一些操作大家都需要调用到,比如字符串操作,图片操作,内存操作,那么这个activity有一份这样的代码,那个activity也有这样一份代码

显然这种情况,如果一直是单人开发,且项目比较小,迭代周期慢,那问题不大,反正就一个人搞,挺好的;但是如果是团队合作开发,且是快速迭代的产品,那每个人面对这样的代码,怎么开发,怎么维护,那将是一个巨大的痛苦啊。

所以这时候就需要引入架构设计使得程序模块化或者说组件化,降低代码的耦合性,加入面向接口的开发,使得开发人员只需要专注自己的开发内容,更加利于后期维护,提高程序开发效率,也方便后续的测试及问题的定位

MVC

MVC全称Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范;它用一种业务逻辑,数据处理,界面显示相分离的方法去组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。

划分

在Android开发过程中,比较流行的项目框架曾经是MVC模式,采用这种模式的好处就是便于UI界面的显示和业务逻辑的分离,具体如下

  • 模型层(Model)
    这里一般就是业务逻辑真正的处理部分了,通常做一些数据处理工作,比如数据库操作,网络请求,复杂算法,耗时操作等,用来描述业务逻辑怎么组合,同时也为数据定义业务规则
  • 视图层(View)
    这里是数据显示的部分,界面就是各种UI组件(XML布局或者Java自定义控件对象),只负责展示数据,同时接收控制器传过来的结果
  • 控制层(Controller)
    这里通常就交给Activity和Fragment了,这里就会处理用户交互问题;比如视图发请求给控制器,由控制器来选择相应的模型来处理;模型返回结果给控制器,由控制器选择合适的视图展示

在Android中的理解

在Android的MVC中,View大多数情况是xml文件,我们通常对View所做的操作逻辑都是写在Activity或者Fragment中,比如点击事件,长按事件等,这样请求事件发送到Controller中,比如点击事件是下载,那么Controller就会将事件转发到Model层去请求数据,Model获取数据后就会通过消息订阅发布机制或者接口去更新View,更新View的操作也是在Activity或者Fragment中操作,这样一个闭环就形成了,View -> Controller -> Model - > View

但是这里View也是可以直接访问Model的,要知道我们自定义的View是Java类,在这个类里是可以直接进行访问Model操作获取数据的,就是绕开了Controller,也是一个闭环,View -> Model - > View

样例

说了这么多,那到底这种模式映射到Android开发中是什么样的呢?

我们就写一个简单的业务功能,情景是用户点击屏幕上的按钮,想要获取自己的用户信息,然后进行网络请求,获取数据后展示在屏幕上

GIF图展示
在这里插入图片描述

工程目录

在这里插入图片描述

编写View层

也就是XML文件

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

    <Button
        android:id="@+id/btn_getuser"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/get_usermsg"
        android:textColor="@android:color/white"
        android:textSize="@dimen/size_14"
        android:paddingLeft="@dimen/paddind_8"
        android:paddingRight="@dimen/paddind_8"
        android:background="@color/colorAccent"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/margin_20"/>
    
    <TextView
        android:id="@+id/tv_msg"
        android:layout_below="@+id/btn_getuser"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingLeft="@dimen/paddind_8"
        android:paddingRight="@dimen/paddind_8"
        android:layout_marginTop="@dimen/margin_20"/>

</RelativeLayout>

里面需要引用到资源文件diemens.xml,strings.xml,colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="margin_20">20dp</dimen>
    <dimen name="size_14">14sp</dimen>
    <dimen name="paddind_8">8dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<resources>
    <string name="get_usermsg">获取用户信息</string>
</resources>

编写Model层

需要一个对象封装用户信息

/**
 * @Description TODO(封装用户信息)
 * @author mango
 * @Date 2018/10/25 10:04
 */
public class User {

	private String userId;
    private String userName;
    private String age;
    private String sex;
    private String job;

	public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
    
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

我们这里使用面向接口方式来开发网络请求操作,先定义一个接口

/**
 * @Description TODO(关于用户数据操作接口)
 * @author cxy
 * @Date 2018/10/25 10:08
 */
public interface UserModel {

    /**
     * @Description TODO(获取用户数据)
     * @author cxy
     * @parame userId 用户索引
     * @parame listener 回调接口
     */
    void getUserMsg(String userId , onUserListener listener);
}

然后定义一个回调接口

/**
 * @Description TODO(用户操作回调接口)
 * @author cxy
 * @Date 2018/10/25 10:12
 */
public interface onUserListener {

    public static final int USERID_IS_EMPTY = 1;
    public static final int USER_IS_INVALID = 2;

    void onBefore();

    void onSuccess(User user);

    void onError(int errorID);
}

接口实现类

/**
 * @Description TODO(用户操作具体实现类)
 * @author cxy
 * @Date 2018/10/25 10:20
 */
public class UserModelImpl implements UserModel {

    @Override
    public void getUserMsg(String userId, final onUserListener listener) {

        if (TextUtils.isEmpty(userId)) {
            listener.onError(USERID_IS_EMPTY);
            return;
        }

        if (listener == null) return;

        listener.onBefore();
        //这里模拟网络耗时操作,实际开发中绝对不要这样直接new Thread
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                User user = new User();
                user.setUserId("12");
                user.setUserName("凯撒");
                user.setAge("35");
                user.setSex("0");
                user.setJob("码农");

                if (user == null) {
                    listener.onError(USER_IS_INVALID);
                } else {
                    listener.onSuccess(user);
                }

            }
        }).start();

    }

}

这里有人可能会说为什么要定义接口做呢,这么麻烦,干脆写个公共的网络操作类,提供一个getUserMsg方法调用就行了;为什么这种方式不可取呢,听我慢慢道来

假如这里网络操作是用原生HttpUrlConnection开发的,然后哪天需要改成OKHttp,难道去修改getUserMsg方法;就算改完后,假如哪天又改成了Retrofit去实现网络操作,难道又去修改getUserMsg方法;这样一连串的迭代,方法被改了多少遍,原来的代码不复存在,假如哪天需要用到以前的,那又得重新编写,这不是遭罪吗

可能有的朋友说,那就写多个getUserMsg方法,面临的问题就是一个类里写了太多这种同一种功能方法,繁杂,对以后的维护不是一个好消息

所以这里就采用面向接口的开发,不管以后的迭代中改成什么需求;比如使用OKHttp请求,那就再定义一个类UserModelWithOkHttpImpl去实现上面的UserModel接口,因为接口是高度抽象的,只定义功能,具体实现因人而定;这样操作以后,不仅保留了以前的代码,而且实现类简洁易维护

编写Controller

public class MainActivity extends AppCompatActivity implements View.OnClickListener, onUserListener {

    private String TAG = MainActivity.class.getSimpleName();

    private final int REQUEST_SUCCESS = 1;
    private final int REQUEST_ERROR = 2;

    private String userID = "12";

    private ProgressDialog dialog;
    private Button btn_getuser;
    private TextView tv_msg;

    private UserModel userModel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        finView();
        initListener();
    }

    private void finView() {
        btn_getuser = (Button) findViewById(R.id.btn_getuser);
        tv_msg = (TextView) findViewById(R.id.tv_msg);
    }

    private void initListener() {
        btn_getuser.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        if (userModel == null) {
            userModel = new UserModelImpl();
        }
        userModel.getUserMsg(userID,this);
    }

    @Override
    public void onBefore() {
        if (dialog == null) {
            dialog = new ProgressDialog(this);
        }
        dialog.show();
    }

    /**
     * 这是是在子线程回调,不能直接更新View
     * @param user
     */
    @Override
    public void onSuccess(User user) {
        Message message = handler.obtainMessage();
        message.obj = user;
        message.what = REQUEST_SUCCESS;
        handler.sendMessage(message);
    }

    /**
     * 这是是在子线程回调,不能直接更新View
     * @param errorID
     */
    @Override
    public void onError(int errorID) {
        handler.sendEmptyMessage(REQUEST_ERROR);
    }

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {
                case REQUEST_SUCCESS:
                    User user = (User) msg.obj;
                    tv_msg.append("姓名:"+user.getUserName()+"\n");
                    tv_msg.append("年龄:"+user.getAge()+"\n");
                    tv_msg.append("职业:"+user.getJob());
                    dialog.dismiss();
                    break;
                case REQUEST_ERROR:
                    tv_msg.setText("");
                    dialog.dismiss();
                    break;
            }
        }
    };

}

这里逻辑挺简单的

  • XML是属于View层,对它的操作放在了Activity
  • 然而Activity作为Controller层,持有UserModel引用(UserModel 作为Model层),接着点击按钮请求用户数据,这样Controller层就向Model层发起了请求
  • 然后Model层进行网络操作,因为可能耗时,所以在真正执行网络前通过回调,通知View层显示加载的dialog,然后获取数据通过回调返回给View层显示出来

就这样一个完整的闭环操作结束了

优点

  • 代码耦合性低,减小模块之间的相互影响;比如同一个数据模型,你可以用柱形图来展示,也可以用圆形图展示,也就是说修改View层的显示效果不用修改数据
  • 可扩展性好,模块之间影响小,加上面向接口的开发,当你新增一个功能或者新增一种功能实现的时候,只需要定义接口和实现接口,就不需要修改以前的代码
  • 模块职责划分明确,如上面所说,每个模块做自己的事
  • 代码维护性好,修改模型不会影响到视图,反过来,修改视图,也不会影响到模型

缺点

MVC其实也有自己的不足,要不然也不会有后来的MVP了

  • 增加了系统结构和实现的复杂性,对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率
  • 视图与控制器间的过于紧密的连接,也就是耦合性高,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用

写这么多了,来,看张图休息会
在这里插入图片描述

MVP

通过上面的例子可以看出来,在MVC中,XML文件作为View层,能做的事实在是有限,比如你想在运行时改变下颜色,字体大小,或者背景啥的,几乎没办法通过修改XML文件去做到,可能有的人会说可以通过修改XML文件字节码,那这就属于杠精了;Activity作为Controller层,做了太多事,首先要加载应用布局,然后初始化界面,View的操作,用户逻辑基本上都在Activity开发

就这样View层的相关操作几乎都放到了Controller层去做,导致Activity的代码非常拥挤,并不是一个标准的MVC模型中的Controller;我之前接手的项目,一个Activity的代码两三千行,逻辑混乱,不管开发还是维护那是巨痛苦的事;如果研究Android源码的同学可能就觉得一个类才两千行代码不奇怪,要知道ViewRootImpl类的performTraversals方法都快一千行了,别说类了,RecyclerView都快上万行了

而且MVC模型中还有一个比较重要的问题就是Model层和View层是可以直接交流的,这就导致两者之间会存在耦合关系;还有Activity即属于Controller,又属于View,那这两层也产生了耦合;对于一个成熟的项目来说,耦合是非常致命的,不管后期开发,维护还是测试都很难继续下去

经过种种探索,MVP就应运而生了

MVP全称Model-View-Presenter,它可以说是MVC的一种进化版本,解决了MVC的不少痛点,不过还是有一定的相似性

划分

  • Model:这里跟MVC中的Model基本一样
  • View:这里就跟MVC中的View不太一样了,不仅仅是XML文件,还包括Activity,Fragment,自定义View等java文件,这样Activity等就可以只负责View的绘制,显示,响应用户的操作
  • Presenter:这就是重点了,作为View与Model之间联系的纽带,让View和Model解耦;同时它与View层是通过接口通信,这又与View层解耦;将原来Activity属于Controller的操作移到了Presenter中

与MVC区别

  • MVP中View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
  • 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
  • Presenter与View的交互是通过接口来进行的,更有利于添加单元测试;而MVC中Activity包含了View和Controller代码,很难做测试

在Android中的理解

在Android中的MVP,XML,Activity,Fragment等都是属于View层,不在执行具体的逻辑,只是纯粹的操作View;具体逻辑交给Presenter处理,View与Presenter通过接口通信,达到解耦;Presenter收到View层请求后转发给相应的Model层,Model层处理完数据后将数据返回给Presenter,然后Presenter再通知View层更新View

样例

现在以一个用户登录的需求为例进行讲解

注意,MVP的核心思想就是一切皆为接口,把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,把数据操作抽象成Model接口

GIF样例
在这里插入图片描述

工程目录结构
在这里插入图片描述

编写View层

首先是xml,很简单 两个输入框 一个按钮,一个提示的textview

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

    <EditText
        android:id="@+id/et_name"
        android:layout_centerHorizontal="true"
        android:layout_width="@dimen/length_200"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:hint="@string/hint_name"
        android:textSize="@dimen/size_14"/>

    <EditText
        android:id="@+id/et_pw"
        android:layout_below="@+id/et_name"
        android:layout_centerHorizontal="true"
        android:layout_width="@dimen/length_200"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:hint="@string/hint_pw"
        android:textSize="@dimen/size_14"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_below="@+id/et_pw"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/margin_20"
        android:text="@string/login"
        android:textSize="@dimen/size_14"
        android:textColor="@android:color/white"
        android:background="@color/colorAccent"/>

    <TextView
        android:id="@+id/tv_hint"
        android:layout_below="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:gravity="center"
        android:textSize="@dimen/size_14"/>

</RelativeLayout>

用到的资源文件如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="length_200">200dp</dimen>
    <dimen name="margin_20">20dp</dimen>
    <dimen name="size_14">14dp</dimen>
</resources>
<resources>

    <string name="hint_name">请输入用户名</string>
    <string name="hint_pw">请输入密码</string>
    <string name="login">登 录</string>

    <string name="login_success">登录成功</string>
    <string name="login_fail">登录失败</string>

    <string name="username_isempty">用户名不能为空</string>
    <string name="userpw_isempty">密码不能为空</string>
</resources>
/**
 * @Description TODO(由View层实现,主要是响应presenter层的操作)
 * @author cxy
 * @Date 2018/10/25 17:15
 */
public interface ILoginView  extends OnProgressListener {

    /**
     * @Description TODO(将View中数据反馈给presenter)
     * @author cxy
     */
    User getUserMsg();

    /**
     * @Description TODO(清除edittext数据)
     * @author cxy
     */
    void clearEdit();

    /**
     * @Description TODO(将登录数据返回给View)
     * @author cxy
     * @parame result 登录返回数据
     */
    void setLoginResult(String result);

}

这就是把UI逻辑抽象成了接口,由Activity去实现

/**
 * @Description TODO(显示进度条 隐藏进度的接口,统一定义,避免每个需要进度显示的接口重复定义这些方法)
 * @author cxy
 * @Date 2018/10/25 17:07
 */
public interface OnProgressListener {

    void onShowProgress();
    void onHideProgress();
}

还有Activity的代码最后放出来,现在放出来有点突然

编写Model层

先定义接口,Model层既然要处理网络操作,那肯定要有网络请求方法,先定义出来,具体谁实现,怎么实现,那是实现的人的事

/**
 * @Description TODO(由相应的model实现,关于用户数据操作接口)
 * @author cxy
 * @Date 2018/10/25 16:55
 */
public interface IUserModel {

    /**
     * @Description TODO(提供网络请求操作)
     * @author cxy
     * @parame url 网络请求链接
     * @parame resultListener 请求回调接口
     */
    void onHttpRequest(String url, OnResultListener resultListener);
}

然后需要去实现

/**
 * @Description TODO(网络请求具体实现类)
 * @author cxy
 * @Date 2018/10/25 17:02
 */
public class UserModelImpl implements IUserModel{

    /**
     * @Description TODO(实现具体的登录逻辑)
     * @author cxy
     * @Date 2018/10/25 17:09
     */
    @Override
    public void onHttpRequest(final String url, final OnResultListener listener) {

        if (listener == null) throw new NullPointerException("OnResultListener is null,this is illegal");
        if (TextUtils.isEmpty(url)) throw new NullPointerException("url is null,this is illegal");

        //这里模拟网络耗时操作,实际开发中绝对不要这样直接new Thread
        new Thread(new Runnable() {
            @Override
            public void run() {

                String result = "";

                //接下来就是网络操作 进行登录 获取登录结果
                try {
                    Thread.sleep(2000);
                    result = "success";
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (TextUtils.isEmpty(result)) {
                    listener.onError();
                } else {
                    listener.onSuccess();
                }

            }
        }).start();

    }

}

编写Presenter层

也是先定义业务逻辑接口,既然登录操作,那就定义就完事了

/**
 * @Description TODO(由相应presenter实现,登录操作对应的presenter,连接View和model)
 * @author cxy
 * @Date 2018/10/25 17:03
 */
public interface ILoginPresenter {

    /**
     * @Description TODO(登录中转点,由View层调用,在该方法中将请求转给model)
     * @author cxy
     * @Date 2018/10/25 17:12
     */
    void onLogin();
}

由相应的presenter实现

/**
 * @Description TODO(登录presenter对应的具体实现类)
 * @author cxy
 * @Date 2018/10/25 17:12
 */
public class LoginPresenter implements ILoginPresenter, OnResultListener {

    private final int REQUEST_SUCCESS = 1;
    private final int REQUEST_ERROR = 2;

    private ILoginView loginView;
    private IUserModel userModel;

    private WeakReference<Context> mContext ;

    public LoginPresenter(Context context,ILoginView loginView) {
        this.loginView = loginView;
        userModel = new UserModelImpl();
        mContext = new WeakReference<>(context);
    }

    @Override
    public void onLogin() {
        loginView.onShowProgress();

        User user = loginView.getUserMsg();
        if (StringUtils.isEmpty(user.getUserName(),user.getPw()) ) {
            handler.sendEmptyMessage(REQUEST_ERROR);
            return;
        }

        String url ;

        //接下来进行登录请求链接的组装
        url = "www.baidu.com";

        userModel.onHttpRequest(url,this);
    }

    @Override
    public void onSuccess() {
        handler.sendEmptyMessage(REQUEST_SUCCESS);
    }

    @Override
    public void onError() {
        handler.sendEmptyMessage(REQUEST_ERROR);
    }

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {
                case REQUEST_SUCCESS:
                    loginView.onHideProgress();
                    loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_success));
                    break;
                case REQUEST_ERROR:
                    loginView.onHideProgress();
                    loginView.clearEdit();
                    loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_fail));
                    break;
            }
        }
    };
}
/**
 * @Description TODO(操作结果回调)
 * @author cxy
 * @Date 2018/10/25 18:38
 */
public interface OnResultListener {

    void onSuccess();

    void onError();

}

可以看到这里持有Model层和View层接口的引用,通过接口获取View层相关数据及控制View的变化,然后通过接口去调用Model层相关方法,最后又通过View层接口将结果返回

还有View层最后一个Activity

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

    private EditText et_name;
    private EditText et_pw;
    private Button btn_login;
    private TextView tv_hint;

    private ProgressDialog dialog;
    private ILoginPresenter loginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        initListener();
    }

    private void findView() {
        et_name = (EditText) findViewById(R.id.et_name);
        et_pw = (EditText) findViewById(R.id.et_pw);
        btn_login = (Button) findViewById(R.id.btn_login);
        tv_hint = (TextView) findViewById(R.id.tv_hint);
    }

    private void initListener() {
        btn_login.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        if (loginPresenter == null) {
            loginPresenter = new LoginPresenter(this,this);
        }
        if (dialog == null) {
            dialog = new ProgressDialog(this);
        }

        if (TextUtils.isEmpty(et_name.getText().toString())) {
            T.show(this, getResources().getString(R.string.username_isempty));
            return;
        }

        if (TextUtils.isEmpty(et_pw.getText().toString())) {
            T.show(this, getResources().getString(R.string.userpw_isempty));
            return;
        }

        loginPresenter.onLogin();
    }

    @Override
    public void onShowProgress() {
        dialog.show();
    }

    @Override
    public void onHideProgress() {
        dialog.dismiss();
    }

    @Override
    public User getUserMsg() {

        User user = new User();
        user.setUserName(et_name.getText().toString());
        user.setPw(et_pw.getText().toString());
        return user;
    }

    @Override
    public void clearEdit() {
        et_name.setText("");
        et_pw.setText("");
    }

    @Override
    public void setLoginResult(String result) {
        tv_hint.setText(result);
    }


}

可以看到,在MainActivity中,只是纯粹的做一些View相关的工作,没有任何业务逻辑,所有用户操作全部转交给presenter去干,这里只是用户点击登录按钮这个业务逻辑,实际上,用户的触屏操作多了去了,比如点击按钮刷新,删除跳转到另外一个页面等,这些所有逻辑都可以在presenter接口中定义相应的方法,然后Activity中去调用就行了

公共类

/**
 * @Description TODO(字符串操作公用类)
 * @author cxy
 * @Date 2018/10/25 19:02
 */
public class StringUtils {

    /**
     * @Description TODO(判断字符串是否为empty)
     * @author cxy
     * @return true 有一个为empty就为true , false 都不为empty就为false
     * @parame 接收一个字符串数组
     */
    public static boolean isEmpty(String... values){

        boolean isEmpty = false;

        for(int i=0; i<values.length; i++){
            if (TextUtils.isEmpty(values[i])) {
                isEmpty = true;
                break;
            }
        }

        return isEmpty;
    }
}
/**
 * @Description TODO(提示公用类)
 * @author cxy
 * @Date 2018/10/25 19:10
 */
public class T {

    public static void show(Context context,String hint){
        Toast.makeText(context,hint,Toast.LENGTH_SHORT).show();
    }

}

通过接口操作后,可以发现View,Model,Presenter三者可以单独工作

没了View,Presenter依然可以工作,在单元测试的时候,比如想测试Presenter中的业务逻辑是否正确,只需要写个脚本模拟Activity,然后在相应的方法中提供数据,最后调用Presenter接口的login方法,看能否得到预期结果,而且两者完成解耦;而且Presenter只专注于业务逻辑,至于页面是什么样的,不关心,所以同一个Presenter可以对应多个View;

去掉Presenter,Model依然可以独立工作,只需要给脚本实现回调接口,然后调用Model接口,传递参数,调用相应的数据操作方法就可以测试Model是否能正常工作,也能实现解耦

用一张图表示MVP

在这里插入图片描述

优点

  • 几乎所有的思想都是为了解耦,更加利于维护和开发,把大工程化整为零,每个团队负责一小部分,相互独立
  • 从上面的样例也可以看出,这种模式下,Activity等View层类的代码相当简洁,基本上只有对View的操作;还有想了解一个模块有哪些业务,就看这个模块的Presenter接口就行了,不管是定位代码还是后续修改业务都很方便
  • 由于面向接口的开发,非常便于单元测试
  • 代码复用程度提高,一个Presenter可以适用于多个View

缺点

  • 从上面的例子和工程目录图明显可以看出来,使用MVP后,类的数量能增加一倍以上,也给维护工作增加了难度,这应该是最大的一个缺点了
  • 维护接口的难度并不低,特别是业务逻辑越来越复杂以后,维护工作更难
  • Presenter有很多连接View与Model之间的操作,造成Presenter的臃肿
  • Presenter和View通过接口通信太繁琐,一旦View层需要的数据变化,那么对应的接口就需要更改

总结

不管是MVC还是MVP都有自己的优点和缺点,没有哪个项目是绝对的适合MVC或者适合MVP,有时候你的项目可能是两者相结合的情况,并且对于使用MVP带来的类数量暴增的情况,需要想办法缓解;可以通过Template自动生成需要的类和接口,这样少去了频繁的复制粘贴;对于一些简单逻辑的页面,可以不使用MVP,直接用MVC;如果有一些页面业务逻辑差不多,可以复用一些Presenter;还可以通过封装基本类,将一些公用方法提取到父类,避免每个接口都去定义这些;或者使用一些第三方框架,比如像支付宝团队使用的TheMVP框架,是通过将Activity或Fragment作为Presenter,将UI操作抽到Delegate中,作为View层;还有一个是通过第三方的消息订阅发布框架或者Handler进行消息通信,代替过多的接口通信,这也能减少不少的类;具体怎么合适,需要自己在实践中多尝试

对于新手来说,刚做项目从零开始,就不要想太多设计架构的问题了,先做出来才是王道,后续再考虑重构的问题,因为所有的优化都是基于你对这些现有模块都很熟悉的情况,如果一开始就想这些,可是都不知道要做成什么样,那什么时候才能写完项目

终于结束了
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/83185033