MVP之高级MVP架构封装

No MVP:

我们一般会这样写:

  1. public class MainActivity extends AppCompatActivity {
  2. private EditText etAccount,etPassWord;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. if (getSharedPreferences("account_sp",MODE_PRIVATE).getBoolean("hadLogin",false)){
  8. Toast.makeText(this,"已经登录了就不用再登录啦",Toast.LENGTH_SHORT).show();
  9. startActivity(new Intent(MainActivity.this,SQLiteActivity.class));
  10. }
  11. etAccount = findViewById(R.id.et_account);
  12. etPassWord = findViewById(R.id.et_pw);
  13. findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
  14. @Override
  15. public void onClick(View view) {
  16. String strAccount = etAccount.getText().toString();
  17. String strPW = etPassWord.getText().toString();
  18. login(strAccount,strPW);
  19. }
  20. });
  21. }
  22. private void login(String strAccount,String strPW){
  23. //模拟执行网络访问,发起登录请求
  24. //模拟网络请求数据的延迟,让当前线程(UI线程)暂停4秒
  25. try {
  26. Thread.sleep(4000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. SharedPreferences sp1 = getSharedPreferences("account_sp",MODE_PRIVATE);
  31. if (strAccount.equals(sp1.getString("account","0"))
  32. &&strPW.equals(sp1.getString("password","0"))){
  33. //如果登录成功
  34. SharedPreferences.Editor editor = sp1.edit();
  35. editor.putBoolean("hadLogin",true);
  36. editor.commit();
  37. Toast.makeText(this,"登录成功",Toast.LENGTH_SHORT).show();
  38. startActivity(new Intent(MainActivity.this,SQLiteActivity.class));
  39. finish();
  40. }else {
  41. //如果登录失败
  42. Toast.makeText(this,"帐号或密码错误",Toast.LENGTH_SHORT).show();
  43. }
  44. }
  45. }

上面的代码是最原始的写法,我们将所有的工作都全部写在了工作Activity中,对于小型项目来说(就比如这个登录),这样写其实更简单直接,但是当项目的业务需求扩展到一定程度的时候,如果继续使用这种写法,缺点如下:

  • 1.代码逻辑复杂且代码量大(一套完整的登录注册修改密码模块可能代码量就会有数百行,试想一下一个类中堆积了几百行绕来绕去的代码后期维护会是什么心情)
  • 2.Activity我们称之为界面,它的工作是显示数据,更新界面,响应用户操作事件,可是在这里,我们还让它承担了网络数据获取,数据解析,业务逻辑处理等工作,这大大的超出了它的本职范围。

MVC:

提到MVP就不得不提到MVC,关于MVC架构,可以看下面这张图

Model View Controller,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见下图当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。

对照上面的登录demo,activity_main.xml里面的xml文件就对应于MVC的view层,里面都是一些view的布局代码,而各种java bean类就对应于model层(假设我们将account和password抽出组合成一个类user,user就是model层),而controller层,就是MainActivity了。

这样解释好像是把一个项目严格的按照MVC三层架构给分开了,但是,结果真的是这样吗?

对button的点击事件是Controller,将account和password抽离组成的user是Model,View呢?V层的工作真的配得上与MC层齐名吗?

而这样写的问题就在于xml作为view层,控制能力实在太弱了,假设在注册成功之前我想弹出一个popupwindow提示用户阅读勾选使用协议,如果不勾选就不给注册,这些都没办法在xml中做,只能把代码写在activity中,造成了activity既是controller层,又是view层的这样一个窘境。

MVC还有一个重要的缺陷,从图中可以看出view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。由于没有相关“大型”项目经验,就不展开赘述。

总而言之最直观的一点,好像我们是给项目分了MVC三层,但实际上呢?只有MC两层,甚至说,只有C(Activity)一层。

普通MVP:

所以基于MVC架构的优化MVP出现了:对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。

从图中可以看出,MVP解决了V和M层存在耦合的问题。虽然V层和M层解耦了,但是V层和P层不是耦合在一起了吗?其实不是的,对于V层和P层的通信,我们是通过接口实现的,具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的Presenter中通过接口调用方法,换言之,V和P层的关系是可控的。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。

  • 1.首先我们先定义一个接口,用来规定针对这个界面逻辑View需要作出的动作的接口。
  • 2.让Activity实现这个接口中的方法,也就是V层。
  • 3.创建一个类,用来封装之前的网络请求过程,也就是M层
  • 4.再创建一个类,用来处理M层和V层之间的通信,也就是P层

现在,我们就来实现这么一个架构:

View层接口:

首先我们先定义一个接口,用来规定针对这个界面逻辑View需要作出的动作的接口。

  1. public interface LoginView {
  2. //请求登录的时候展示加载(比如说展示progressBar)
  3. void Logining();
  4. //请求登录成功,一般后台会返回一个User信息的json数据
  5. void LoginSuccess(User user);
  6. //请求登录失败,(比如弹出一个错误提示框)
  7. void LoginFailure(User user);
  8. }

如何去设计View层接口,根据实际需求,根据View层对应的显示逻辑是什么去设计,比如说,一个注册功能(包含有图形验证码的验证),我需要响应:

  • 验证码展示
  • 验证码验证成功后可以注册
  • 验证码验证失败弹出提示
  • 注册成功
  • 注册失败
  • 其他操作……

那么我们就需要至少5个方法一一对应View层的显示逻辑。

View层:

View层实现对应接口的方法即可,在里面处理各种View的逻辑。

值得注意的地方在:

  • 1.Activity需要实现v层接口

    • 2.在实现类view中创建persenter
  1. public class MVPLoginActivity extends AppCompatActivity implements LoginView{
  2. private LoginPresenter loginPresenter;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. //初始化presenter
  6. loginPresenter = new LoginPresenter(this);
  7. //调用presenter管理的登录方法
  8. loginPresenter.clickToLogin(new User(etAccount.getText().toString(),etPassWord.getText().toString()));
  9. }
  10. @Override
  11. public void Logining() {
  12. //展示正在登录;
  13. }
  14. @Override
  15. public void LoginSuccess(User user) {
  16. //登录成功后展示成功的信息
  17. }
  18. @Override
  19. public void LoginFailure(User user) {
  20. //失败后展示失败的信息
  21. }
  22. }

Model层:

创建一个类,用来封装之前的网络请求过程,也就是M层,M层真正访问数据的地方,比如说在这里完成向后台获取数据,解析数据,然后可以返回给Presenter,交由Presenter中转View显示数据。

  1. public class LoginModel {
  2. public User Login(LOGIN_ARGS){
  3. if (LOGIN_SUCCESS){
  4. //模拟后台返回成功的数据
  5. return SUCCESS_INFO;
  6. }else if(LOGIN_FAILED){
  7. //模拟后台返回失败的数据
  8. return FAILED_INFO;
  9. }
  10. }
  11. }

Presenter层:

再创建一个类,用来处理M层和V层之间的通信即Presenter层,调用Model层给的数据相关接口实现登录或者数据获取等操作,桥接对应的View层逻辑。

1.Persenter需要持有v层引用和m层引用

  1. public class LoginPresenter {
  2. private LoginView loginView;
  3. private LoginModel loginModel;
  4. public LoginPresenter(LoginView loginView){
  5. this.loginView = loginView;
  6. loginModel = new LoginModel();
  7. }
  8. public void clickToLogin(User user){
  9. //提示正在登录
  10. loginView.Logining();
  11. //通过Model层登录
  12. User user1 = loginModel.Login(user.getAccount(), user.getPassword());
  13. //登录成功
  14. {loginView.LoginSuccess(user1);}
  15. //登录失败
  16. {loginView.LoginFailure(user1);}
  17. }
  18. }

普通MVP的小结:

在使用MVP架构之后,我们的Activity不在是啥都做了,里面的逻辑很清晰,每一种操作都对应到了封装的方法可是这样写会内存泄露,例如在网络请求登录数据的过程中Activity就关闭了,Presenter对象还持有了V层的引用,也就是MVPLoginActivity,就会导致内存泄露。因为这里的Demo很简单,无法体现这种影响。

更形象的描述是:当Presenter对象持有一个或多个大型Activity的引用,如果该对象(P)不能被系统回收,那么当这些Activity不再使用时,这个Activity也不会被系统回收,这样一来便出现了内存泄漏的情况。在应用中内出现一次两次的内存泄漏或许不会出现什么影响,但是在应用长时间使用以后,若是这些占据大量内存的Activity无法被GC回收的话,最终会导致OOM的出现,就会直接Crush应用。

解决了内存泄露的MVP:

实现的思路就是,我们将Presenter的生命周期和View层的生命周期绑定在一起,给Presenter定义两个方法,一个绑定View层,一个解绑View层,在需要的时候进行绑定,不需要的时候进行解绑就可以了。

之前是在Presenter的构造方法中传递View层,那么现在不需要在构造函数中传递V层了,直接在需要创建Presenter的地方使用绑定的方法即可,在Activity的onDestroy方法中进行解绑定。

修改后的Presenter层:

  1. //绑定
  2. public void attachView(LoginView loginView){
  3. this.loginView = loginView;
  4. }
  5. //解绑定
  6. public void detachView(){
  7. loginView = null;
  8. }
  9. public void interruptRequest(){
  10. //中断model的网络请求
  11. loginModel.interruptRequest();
  12. }

我们给Presenter层额外设置了三个方法,分别是绑定和解绑定,以及在某些情况下我们需要中断网络请求操作的方法,在该方法中中断Model层的网络访问。来实现三层同步。

修改后的View层:

  1. public class MVPLoginActivity extends AppCompatActivity implements LoginView{
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. //loginPresenter = new LoginPresenter(this);
  5. loginPresenter = new LoginPresenter();
  6. loginPresenter.attachView(this);
  7. }
  8. @Override
  9. protected void onDestroy() {
  10. super.onDestroy();
  11. loginPresenter.detachView();
  12. loginPresenter.interruptRequest();
  13. }
  14. }

## 继续优化后的MVP:

在上面一步中,通过给Presenter层设置绑定和解绑View层的对应方法,解决了内存泄露的问题,但是这样并不够,一个APP中肯定不可能只有一个简单的登录模块,基于MVP使得我们每个功能模块都对应着一个V层和P层,那这样的话每个Presenter中都要定义绑定和解绑的方法,而Activity中对应的也要调用这绑定和解绑的两个方法,在这里导致了另一个问题:代码冗余。

要解决这个问题我们可以抽取出一个基类的Presenter和一个基类的Activity来做这个事情,让子类不用在写这些重复的代码。

那么随之而来的就是另一个问题:既然是基类,肯定不止有一个子类来继承它,子类当中定义的View接口和需要创建的Presenter都各不相同,我们无法将基类写死,解决的办法就是使用泛型。

基类View层接口:

创建一个基类接口BaseView,这个View可以什么都不做只是用来约束类型的

  1. public interface BaseView {
  2. }

基类Presenter:

创建一个基类的BasePresenter,在类上规定View的泛型,然后定义绑定和解绑的抽象方法,让子类去实现,对外在提供一个获取View的方法,

让子类直接通过方法来获取View

  1. public abstract class BasePresenter<V extends BaseView> {
  2. private V mMvpView;
  3. public void attachView(V view){
  4. this.mMvpView = view;
  5. }
  6. public void detachView(){
  7. mMvpView = null;
  8. }
  9. public V getmMvpView() {
  10. return mMvpView;
  11. }
  12. }

基类View层:

创建一个抽象基类的BaseActivity,声明一个创建Presenter的抽象方法,因为要帮子类去绑定和解绑那么就需要拿到子类的Presenter才行,但是又不能随便一个类都能绑定的,因为只有基类的Presenter中才定义了绑定和解绑的方法,所以同样的在类上可以声明泛型在,方法上使用泛型来达到目的。

  1. public abstract class BaseActivity<V extends BaseView,P extends BasePresenter<V>>
  2. extends AppCompatActivity implements BaseView{
  3. private P presenter;
  4. @Override
  5. protected void onCreate(@Nullable Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. if (presenter==null){
  8. presenter = createPresenter();
  9. presenter.attachView((V) this);
  10. }else {
  11. presenter.attachView((V) this);
  12. }
  13. }
  14. @Override
  15. protected void onDestroy() {
  16. super.onDestroy();
  17. if (presenter!=null){
  18. presenter.detachView();
  19. }
  20. }
  21. public abstract P createPresenter();
  22. public P getPresenter() {
  23. return presenter;
  24. }
  25. }

最后三层各自继承各自的基类即可

总结

从MVC到最简单的MVP架构,我们解决了MVC的数据层和视图层耦合的问题;随之而来的是内存泄露的问题,通过设置对应的绑定解绑方法来解决这个问题;之后又是代码冗余的问题,于是利用Java的多态性,我们将重复性工作交由基类去完成,子类继承基类重写对应方法即可。而实际上

我们只需要修改上面Presenter中的构造代码,不需要在构造中传递V层了,然后再写一个绑定和解绑的方法,最后修改Activity创建Presenter时进行绑定,在onDestroy中进行解绑。

MVP之高级MVP架构封装的更多相关文章

  1. 2020年如何成为一个高级AVA架构师(50W~100W年薪)

    2020年如何成为一个高级AVA架构师(50W~100W年薪)

  2. 百度架构师带你进阶高级JAVA架构,让你快速从代码开发者成长为系统架构者

    百度架构师带你进阶高级JAVA架构,让你快速从代码开发者成长为系统架构者 1.

  3. .NET框架设计(高级框架架构模式)—钝化程序、逻辑冻结、冻结程序的延续、瞬间转移

    阅读目录: 1.开篇介绍 2.程序书签(代码书签机制) 2.1ProgramBookmark 实现(使用委托来锚点代码书签) 2.2ProgramBookmarkManager书签管理器(对象化书签集 ...

  4. MVP ComCamp & GCR MVP Openday 2014

    今年的MVP Openday与往年不一样,加入了Community Camp环节,即社区大课堂.其主要形式是由MVP作为讲师提供包括Developer和IT Pro方向的课程,地点是在北京国际会议中心 ...

  5. JS高级-数据结构的封装

    最近在看了<数据结构与算法JavaScript描述>这本书,对大学里学的数据结构做了一次复习(其实差不多忘干净了,哈哈).如果能将这些知识捡起来,融入到实际工作当中,估计编码水平将是一次质 ...

  6. 编程心法 之什么是MVP What is MVP development?

    Minimal Value product(feather), 比如说,如果是一个新的Photoshop,那么增加图片亮度就是一个MVP. 想要看到更多玮哥的学习笔记.考试复习资料.面试准备资料?想要 ...

  7. 成为一个高级java架构师所需要具备那些技能呢?

    一.什么是架构师 所谓架构师,思考的是全局的东西,是如何组织你的系统,以达到业务要求,性能要求,具备可扩展性(scalability),可拓展性(extendability),前后兼容性等.可能涉及到 ...

  8. 【MySQL 高级】架构介绍

    MySQL高级 架构介绍 MySQL 简介 MySQL 安装 Docker 安装 参考链接 Linux 安装 参考链接 MySQL 配置文件 log-bin:二进制日志文件.用于主从复制.它记录了用户 ...

  9. js高级---js架构

    ECMAScript1997 年欧洲计算机制造商协会 39 号技术委员会制定了ECMA-262标准(别名 ECMAScript),而浏览器只是负责实现,ie浏览器实现的结果是jscript,远景浏览器 ...

随机推荐

  1. npm命令

    简介:npm(node.js package manager)是Node.js的包管理器 .它创建于2009年,作为一个 开源项目,帮助开发人员轻松共享打包的代码模块 ## 默认方式初始化npm.(进 ...

  2. electron 打包流程 electron-packager + NSIS

    1.安装 electron-packager 2.electron-packager 应用目录 应用名称 打包平台  左上角的图标和任务栏的图标  输出目录 架构 版本     win打包:  ele ...

  3. bzoj 3704 昊昊的机油之GRST - 贪心

    题目传送门 传送门 题目大意 给定一个数组$a$和数组$b$,每次操作可以选择$a$的一个子区间将其中的数在模4意义下加1,问把$a$变成$b$的最少操作次数. 首先求$b - a$,再差分,令这个数 ...

  4. 直方图均衡化与Matlab代码实现

    昨天说了,今天要好好的来解释说明一下直方图均衡化.并且通过不调用histeq函数来实现直方图的均衡化. 一.直方图均衡化概述 直方图均衡化(Histogram Equalization) 又称直方图平 ...

  5. cv2.getRotationMatrix2D函数

  6. IDEA汉化教程

    https://blog.csdn.net/weixin_38500325/article/details/81393251

  7. Java面向对象内存分析

    title: Java面向对象内存分析 date: 2018-07-28 11:12:50 tags: JavaSE categories: - Java - JavaSE 一.Java虚拟机的内存区 ...

  8. javascript 之 函数

    注意:函数名仅仅是一个包含指针的变量而已 函数内部属性 arguments 和this 两个特殊对象 arguments:类数组对象,包含出入函数中的所有参数,主要用途是保存函数参数 callee:该 ...

  9. spring事务的7种传播行为

    https://blog.csdn.net/weixin_39625809/article/details/80707695 一般用于并发,分布式锁.复杂业务情况

  10. JS的Date对象、Math、包装类

    Date对象 在JS使用Date对象来表示时间  当前时间 var d = new Date();  指定时间 格式:月/日/年 时:分:秒 var e = new Date("02/16/ ...