Android项目的MVC与MVP

目录

一 MVC

1.概念

2.实例

(1)Model层

(2)Controller层(包括View层)

3.MVC总结

二 MVP

1.概念

2.实例

(1)Model层

(2)View层

(3)Presenter层

3.总结

(1)MVP优点

(2)与MVC的对比

三 总结


做了Android开发很长时间了,从毕业就一直从事Android开发。现在在翻看自己以前项目中的代码,发现自己以前想法写到代码不是一般的烂。最近也在做一些技术沉淀,发现需要学习的东西还有好多,每天觉得时间飞快。最近也是将公司项目架构调整了一下。以前项目也就是简单的MVC架构模式,当公司业务越来越复杂的时候,发现这种分层方式的弊端越来越明显,Activity中堆积了大量的代码,业务复杂的Activity,代码都有几千行。代码可读性极差,后期维护性差,想想如果哪天产品要对页面UI进行升级的话,其实改造成本还蛮高的,Activity代码逻辑复杂,可重用性不高,所以针对这种现象,将公司项目架构做了调整。

一 MVC

1.概念

在Android开发的界面显示,通常在开发过程中都会将网络层请求封装成可以之间调用的方法供Activity直接调用,其实就可以看作是一个简单的MVC架构模式。简单的与MVC的三层进行对比下

View(视图)层:主要就是布局文件或者使用java代码实现的自定义view;

Model(模型)层:主要就是渲染UI时使用的数据model、网络请求、数据库增删改查、IO等操作封装对应的相关模块;

Controller(控制)层:主要Activity承担该角色,用来初始化Model层,初始化、加载View层,从而控制View层和Model层。当用户在Activity通过View层触发事件的时候,Activity(Controller层)就会调用Model层的相关代码来获取渲染UI使用的数据,从而达到改变UI,完成用户的交互。

其实在Android中,由于布局文件无法进行一些逻辑和交互,而Activity去承担了View的一些UI渲染和交互的工作,所以在Android中MVC更像是Controller和Model之间的一个数据流向。

通常会将Model层设计成ModelInteface的方式,不对Controller层(Activity)提供具体的实现,这样可以提高Model层的代码的扩展和维护性。举个例子Model层通常用来网络请求,说不定哪天就会更新网络请求的具体实现方式。那么如果通过ModelInteface的方式,那么我们只需要在最新的网络请求方式上去实现ModelInteface,而不需要更改Controller层的代码。

2.实例

举个代码实例:例如有一个登录的界面,用户输入账号和密码就可以完成登录,返回用户的信息更新到UI界面上(PS:实例代码中都是模拟网络请求过程,仅仅用来说明MVC的一种代码实现方式)。

public class MvcCacheBean implements IViewCacheBean {
    public User user;
}

(1)Model层

  • 定义ModelInterface接口,供Controller层调用。
public interface IMvcModelInterface {
    /**
     * 登录
     *
     * @param account
     * @param password
     * @param result   服务器处理完数据返回的结果
     */
    void login(String account, String password, IHttpResult result);
}
  • 定义MvcCacheBean,用来接收服务器返回的数据
public class MvcCacheBean implements IViewCacheBean {
    public User user;
}
  •  实现ModelInterface接口,完成网络请求。

其中的Thread与Handler仅仅用来模拟网络请求的过程,在实际项目中肯定对应这相应封装的网络请求模块对应的API。

public class MvcModelImpl implements IMvcModelInterface {
    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    //仅仅简单的做个逻辑判断,只是用来返回成功和失败的回调
                    int arg1 = msg.arg1;
                    IHttpResult result = (IHttpResult) msg.obj;
                    if (arg1 == 1) {
                        User user = new User();
                        user.name = "小刘";
                        user.sex = "女";
                        user.age = "34";
                        MvcCacheBean cacheBean = new MvcCacheBean();
                        cacheBean.user = user;
                        result.success(cacheBean);
                    } else {
                        result.failure("登录失败\n账号输入数字即可验证成功的逻辑");
                    }
                    break;
            }
        }
    };

    @Override
    public void login(String account, String password, IHttpResult result) {
        //模拟具体的网络请求方式的实现:子线程去请求数据,数据处理完之后返回的UI线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message msg = new Message();
                msg.what = 1;
                msg.obj = result;
                msg.arg1 = TextUtils.isDigitsOnly(account) ? 1 : 2;
                handler.sendMessage(msg);

            }
        }).start();
    }
}

(2)Controller层(包括View层)

在Activity中加载布局文件,实例化控件,并且实例化Model层。当用户填写账号和密码之后,点击Button,去完成网络请求,返回用户信息。

public class MvcActivity extends Activity {
    private IMvcModelInterface model;
    private TextView tvUserInfo;
    private EditText etAccount;
    private EditText etPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        //初始化View
        initWidget();
        //初始化Model
        model = new MvcModelImpl();
    }

    private void initWidget() {
        tvUserInfo = findViewById(R.id.tv_user_info);
        etAccount = findViewById(R.id.et_account);
        etPassword = findViewById(R.id.et_password);
    }

    /**
     * 登录button的点击事件
     *
     * @param view
     */
    public void btnLogin(View view) {
        tvUserInfo.setText("稍等1s之后就可以看到模拟的结果");
        tvUserInfo.setTextColor(Color.BLACK);
        String account = etAccount.getText().toString();
        String password = etPassword.getText().toString();
        if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
            Toast.makeText(MvcActivity.this, "账号和密码不能为空", Toast.LENGTH_SHORT).show();
            return;
        }
        //调用Model层进行网络请求
        model.login(account, password, new IHttpResult() {
            @Override
            public void success(IViewCacheBean cacheBean) {
                MvcCacheBean mvcCacheBean = (MvcCacheBean) cacheBean;
                User user = mvcCacheBean.user;
                tvUserInfo.setText(String.format("用户登录成功\n用户名:%s\n性别:%s\n年龄:%s", user.name, user.sex, user.age));
                tvUserInfo.setTextColor(Color.GREEN);
            }

            @Override
            public void failure(String error) {
                tvUserInfo.setText(error);
                tvUserInfo.setTextColor(Color.RED);
            }
        });
    }
}

从代码实例中分析事件流向:Activity负责初始化Model层和View层(Controller层控制Model层和View层),当用户触发Button的点击事件(View层产生事件),Activity会调用Model层的相关代码(Controller层向Model层发出请求),当Model层完成业务逻辑,会回调给Activity的成功和失败的结果(Model层处理完数据后,向Controller层通知事件处理完),Activity更新TextiVew的显示(Controller层通知View层处理事件),这样就完成了一个MVC的事件流向的过程。

3.MVC总结

(1)具有一定分层,Model层彻底解耦,但是Controller层和View层却无法解耦

(2)层与层之间尽量使用回调、消息机制,避免直接持有

(3)在Android中的Activity承担了太多的工作,既有Controller层功能,也要承担View层的功能,所以当UI界面越来越复杂,功能越来越多的时候,造成Activity的代码臃肿。

二 MVP

1.概念

在Android开发中正因为Activity承担了太多的工作,既要初始化UI,还要去处理用户的交互,并不能单纯的去做Controller,造成Activity代码臃肿,所以就演变除了MVP。

View(视图)层:主要就是布局文件或者使用java代码实现的自定义view,更准确的说其实是Activity和布局文件共同来承担该角色,用来初始化UI和UI交互的工作;

Model(模型)层:和MVC中的Model层功能一致,仍然是渲染UI时使用的数据model、网络请求、数据库增删改查、IO等操作封装对应的相关模块;

Presenter层:作为View层和Model层的中间纽带,用来出来用户交互逻辑,通常持有的是View 的ViewInterface,Presenter通过ViewInterface来处理View层的交互,降低与View层的耦合,更可以不依赖于View,单独对Presenter进行单元测试。

ViewInterface:就是View要实现的接口类。该接口类主要工作就是View的交互事件,我们可以通过该接口类,将Presenter不依赖于View层代码。

另外我觉得可以在增加一个Presenter层增加一个PresenterInterface,这样可以规范Presenter代码,在后面提到的代码生成工具中会有比较好的作用。

那么MVP的三层之间的关系就变成了下图的事件流向:

在MVP中,我们将之前MVC中Activity中的一部分逻辑转移到Presenter中,Activity只需要去初始化UI和完成控件本身的事件监听,当需要逻辑处理的时候,直接调用Presenter层进行处理,而Presenter层直接去调用Model层的代码去完成数据请求;当Model层处理完数据之后,再有Presenter层来通知View层更新UI。现在我们将Activity的控制权交到了Presenter层,而Activity更多的用来转发请求。

2.实例

仍然通过上面的一个实例简单的说明下MVP的一个架构情况。其实我们从第一部分的概念描述中可以看到,其实MVP和MVC的View层和Model层的功能是一样的,为了主要突出他们有区别的地方,所以在MVP中仍然采用MVC的Model层和View层的代码。

(1)Model层

同MVC中的Model层

(2)View层

这次Activity和布局文件同时承担View层的工作。

  • ViewInterface将View层和Presenter层进行解耦

在介绍Presenter层代码的时候,会发现Presenter层持有的只有该ViewInterface,可以使得Presenter更加灵活。

public interface IMvpViewInterface {
    /**
     * 接口登录成功回调
     * @param cacheBean
     */
    void loginSuccess(MvcCacheBean cacheBean);

    /**
     * 接口登录失败的回调
     * @param error
     */
    void loginFailure(String error);
}
  • 在Activity中进行实例化View和简单的响应View的监听事件 
public class MvpActivity extends Activity implements IMvpViewInterface {
    private MvpPresenter presenter;
    private TextView tvUserInfo;
    private EditText etAccount;
    private EditText etPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        //初始化View
        initWidget();
        //初始化Presenter
        presenter = new MvpPresenter(MvpActivity.this);
    }

    private void initWidget() {
        tvUserInfo = findViewById(R.id.tv_user_info);
        etAccount = findViewById(R.id.et_account);
        etPassword = findViewById(R.id.et_password);
    }

    public void btnLogin(View view) {
        tvUserInfo.setText("稍等1s之后就可以看到模拟的结果");
        tvUserInfo.setTextColor(Color.BLACK);
        String account = etAccount.getText().toString();
        String password = etPassword.getText().toString();
        //View不需要关系业务逻辑,只需到时候在回调的方法里面填写代码即可
        presenter.login(account, password);
    }

    /**
     * 只需要在成功和失败回调之后处理UI,使代码更加简洁
     *
     * @param cacheBean
     */
    @Override
    public void loginSuccess(MvcCacheBean cacheBean) {
        MvcCacheBean mvpCacheBean = (MvcCacheBean) cacheBean;
        User user = mvpCacheBean.user;
        tvUserInfo.setText(String.format("用户登录成功\n用户名:%s\n性别:%s\n年龄:%s", user.name, user.sex, user.age));
        tvUserInfo.setTextColor(Color.GREEN);
    }

    @Override
    public void loginFailure(String error) {
        tvUserInfo.setText(error);
        tvUserInfo.setTextColor(Color.RED);
    }
}

(3)Presenter层

  • PresenterInterface:使得Presenter层的代码更加简洁,便于代码规范化。

因为我们项目现在页面大部分代码是可以直接通过自研的工具生成,通过接口规范化Android和iOS两端的代码,那么开发人员可以只需要关注View层代码逻辑即可。并且可以让一个人同时开发两端代码,因为差别在于View层的部分逻辑的差异。

public interface IMvpPresenterInterface {
    void login(String account, String password);
}
  • Presenter实现类实现去调用Model层,并完成与View层的回调

在Presenter中仅仅持有ViewInterface,那么一个Presenter可以应用在多个View,例如项目中肯定有一些功能是可以用在不同页面的,那么就可以把这些功能放到一个Presenter中,只需要View实现对应的ViewInterface,那么该功能就可以直接在View中使用。

public class MvpPresenter implements IMvpPresenterInterface {
    private IMvpViewInterface mvpViewInterface;
    private IMvcModelInterface model;
    private Context context;

    public MvpPresenter(IMvpViewInterface viewInterface) {
        this.mvpViewInterface = viewInterface;
        this.context = (Context) viewInterface;
        //实例化Model
        model = new MvcModelImpl();
    }

    @Override
    public void login(String account, String password) {
        //部分UI逻辑可以转移到MvpPresenter,来减轻Activity的负担
        if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
            Toast.makeText(context, "账号和密码不能为空", Toast.LENGTH_SHORT).show();
            return;
        }
        model.login(account, password, new IHttpResult() {
            @Override
            public void success(IViewCacheBean cacheBean) {
                mvpViewInterface.loginSuccess((MvcCacheBean) cacheBean);
            }

            @Override
            public void failure(String error) {
                mvpViewInterface.loginFailure(error);
            }
        });
    }
}

从代码中来分析事件的流向:Activity负责初始化View(View层),同样还是用户点击了这个Button,就会调用到Presenter层相关代码,而Presenter层接到请求之后,会调用Model层的代码来完成数据请求,Model层响应数据之后,Presenter层又会调用到View层的相应的方法完成回调。

现在Activity(View层)仅仅就是View的展示以及View的交互,Activity(View层)不在关心处理的逻辑过程是什么,只需要去做初始化,事件的监听,在对应的回调方法中处理UI显示就可以了,Activity(View层)责任大大减少;

而现在Presenter是整个控制中心,不仅要去决定请求Model层的哪个代码,并且Model层返回数据之后,Prensenter还要决定回调View层的哪个方法,Activity(View层)和Model层不需要做什么,Presenter会安排Activity(View层)和Model层要做什么。

Activity(View层)仅仅就是单独的UI操作,而Presenter来处理交互逻辑,并负责协调Model层和View层。 与上面MVC的代码对比,Activity代码更加简洁。

3.总结

(1)MVP优点

  • 模型和视图完全分离,两者互相修改不会受影响;
  • 一个Presenter可以应用在多个视图,而不需要改变Presenter的逻辑,同样一个View也可以拥有多个Presenter来解决View复杂的页面功能;
  • 视图与Presenter解耦,可以脱离视图,对Presenter进行单元测试;
  • Presenter是整个架构的控制者,模型和视图不用关心怎么做,Presenter会告诉该怎么做。

(2)与MVC的对比

  • 在MVC中,Activity是控制者,负责去调度View层和Model层;而MVP中,Presenter是控制者,Activity层仅仅负责转发;
  • 在MVC中,View层和Model层可以直接交互;而在MVP中,View层和Model层不能直接交互,只有通过Presenter层;
  • 在MVC中,Activity中承担Controller层和View层功能,Controller层和View层互相耦合;而在MVP中Presenter层作为控制者,以及和View层完全解耦,可以不依赖于View层进行单元测试。

三 总结

MVVM是对MVP的再一次升级,其中VM为ViewModel,可以理解为View的数据模型和Presenter的合体。我觉得这样可以拿着微信小程序的开发方式考虑一下。一个页面对应着四个文件:.js文件:主要负责业务逻辑;.wxml:负责页面;.wxss:负责页面样式,修饰页面;.json:负责页面的一些简单配置。我觉得里面就有一个很意思的东西,我们通常都是在.js中定义一个变量,wxml文件中引用该变量,当我们在js中修改该变量的值的时候,wxml文件中自动反馈出该变化,我觉得MVVM应该差不多就是这个意思。

对比下三者之间的异同点:

相同点:三者对应的Model层和View层的功能是相同的。View层就是UI以及一些相关的界面逻辑代码;Model层就是数据对象和对网络请求、数据库等操作。

不同点:怎么将View层和Model层进行通信

(1)MVC:通过Controller层,负责对View层进行初始化和对Model层的操作,Model层的数据变更并不通过Controller层,而是直接通过回调通知View层。

(2)MVP:通过Presenter层来接收View层变化,并决定操作那个Model;而Model数据处理完之后,Presenter层又会去决定操作哪个View来完成整个事件。

(3)MVVM:VM中不仅仅包含View的一些数据属性还包含一些操作,就是微信小程序的wxml文件和js文件,View层的变化会直接影响到ViewModel层,同样ViewModel层的变化也直接反馈到View层。

因为项目最终采用了MVP的架构方式,并且基于这种架构方式,制定了一套开发规范,自研了一套开发工具,可以生成Android和iOS项目使用的Model层、View层以及Presenter层的代码,那么作为开发人员只需要关注View层代码,并且可以同时开发两端的代码,因为差别也就是在View层。

最后本文中的代码已经上传到github,可以自行下载去运行: https://github.com/wenjing-bonnie/MvcMvp.git

猜你喜欢

转载自blog.csdn.net/nihaomabmt/article/details/113371958