MVP那些事儿 (2) 初探MVC架构

2,351 阅读13分钟

目录

MVP那些事儿(1)……用场景说话

MVP那些事儿(2)……MVC架构初探

MVP那些事儿(3)……在Android中使用MVC(上)

MVP那些事儿(4)……在Android中使用MVC(下)

MVP那些事儿(5)……中介者模式与MVP的关系

MVP那些事儿(6)……MVC变身为MVP

MVP那些事儿(7)……Repository设计分析

为什么要先介绍MVC?

如果你要想更佳深刻的理解MVP,并在实际开发中灵活的应用,那么就要先了解它的低配版MVC,他俩只是一步之遥,先了解MVC再学习MVP,MVP的优势才能凸显出来,这样连贯性的学习才会加深对MVP的理解。

我们在上一章提到,MVP那些事儿(1) 用场景说话 对我们的案例进行MVC的设计,也就是第一步,如何实现一个MVC,在实现之前我们先搞清楚几点概念。

MVC的作用?它是设计模式吗?是框架吗?是架构吗?

MVC属于分层架构中的一员,无论是哪一个层从复用性和扩展性都是由当前业务所限制的,如果一开始就从复用和扩展上作为出发点来使用MVC的话,一定要有足够的开发经验(并不是不可以,分层架构也是可以有扩展性的,但要考虑主次),除非你非常明确未来软件的复杂度。举个例子,比如有一个算法,它处理一组数据所需的时间关系如下:10条,需要1秒,20条需要1.8秒,30条需要2.5秒,,,,100条需要6秒,这个时间复杂度可以看出什么,可以看出这个算法处理的数据量越大,它的处理所消耗的时间递增越缓慢,也就越是大数据量越能体现它的价值,再举一个实际中的例子,人均100的餐厅,两个人去也许每个人只能尝到2~3种菜样,但四个人去,每个人会尝到4~6菜样,20个人去,你也许可以把他们家的菜尝个遍,MVC也是如此,软件的复杂度越高,越能体现MVC的扩展价值,这就是我为什么要讲不要一上来就想从扩展性作为使用MVC的出发点,除非你是一个很有经验的人,能提前估计出项目的一个大致的复杂度。更稳妥的方式应该是在项目的推进中不断的优化自己的架构,很多萌新会进入到误区,一看MVP大家都在用,自己设计一个吧,到最后发现根本就你一个人在吃菜,更关键的是别人想和你吃也吃不了,不通用,根本无法体现它的扩展价值,负责人发现后第一反应是,你搞这么多接口抽象类干什么?就你一个人用,搞事情。

MVC不是简单的设计模式

一个只为了分层就可以被叫做设计模式的话,那么外观模式看来还不是最尴尬的模式,所以它不能简单的称呼为设计模式,因为它的范围和深度更加立体,称为架构更为妥当,当然也可以称之为复合设计模式,它里面包含了,策略,组合,观察者,等设计模式。那么,架构和框架的关系又是什么呢?

架构和框架说的不是一回事儿

MVC是一种架构思想,而基于这个思想的框架也叫MVC框架,在ios的开发中,系统为我们实现了公共的视图类UIView,和控制器类UIViewController,还有大名鼎鼎的Struts2框架,都是基于MVC架构设计出来的框架(基于MVVM架构的DataBinding,Architecture Components框架),框架是一个有边界的可扩展集,它在可扩展的同时也是有边界的,也就是说你只能在这个框架里玩耍,而无法超出这个框框。如果你只是在项目中使用了MVC的思想去做分层,那么这个项目或模块用的就是MVC架构设计,如果你用了Struts框架,那么这个项目使用了MVC框架。当你向别人介绍你的项目时,你可以这么说:我的项目使用的是MVVM架构,具体用到了Architecture Components作为框架来进行开发,千万不要说成:大家好,我来介绍一款基于MVVM框架设计的Architecture Components架构,这听的得多别扭啊。

一句话总结——架构是蓝图,而框架是实实在在的产品,MVP架构就一个,而基于MVP架构设计的框架可以是千千万万个。

以上就是对MVC的一个大致的介绍,学习MVC之前,要先会写,学会了写,再学习用,接下来我们先实现一个MVC框架。

实现MVC什么?MVC(框架)

在第一章的场景里我们挑一个需求,我们来实现一个向服务端请求数据,并显示在列表的需求。

需求时序图 在Android中我们实现上面的需求,通常情况是在界面的Create时,调用一个请求接口,通过异步的方式返回数据,待数据返回时更新UI,我们用一个Activity来实现:

public class TasksActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        //...初始化
        //请求列表数据
        Data data = loadData();
    }
    void onDataCallBack (Data data) {
        //更新列表
        upDateList(data);
    }
}

这是一段简单的示例代码,通常情况下我们用像loadData这样的方法去请求数据,并且绑定监听,监听数据的返回,当数据返回时,像onDataCallBack这样的回调方法会被触发,同时进行ui的更新。

同样的需求,用MVC写一遍

MVC分别为Model,View,Controller,顾名思义,首先我们需要创建这三个对象,第二步,我们把这三个对象组合起来,我们先一个一个的认识它们,逐个击破,首先从简单的来,Model,

什么是Model,它的职责

逻辑层(领域层)Domain Object,逻辑执行体,除此之外也可以兼顾JavaBean的职责,但Model层可不是简单的Value Object。而 JavaBean也就是Value Object,只是用来封装数据,不具备Model层的职责,当然也可以把JavaBean当作Model类的一个内部类来使用,当然也可以单独一个类,通常我们也会这么作,方便复用。 萌新⚠️

1、JavaBean不是Model,但Model也可以包含JavaBean的职责,但不是必须的。 2、Model是用来处理数据的,如获取数据(本地或者服务端),数据处理,如CURD。

按照上面的定义我们来写一个Model

/**我是一个Model**/
public class TasksRepository {
    //从服务器请求获取数据
    void getTasks() {}
    //从内存缓存获取数据
    Data getTaskCache() {}
    //从磁盘缓存获取数据
    Data getTaskDiskCache(){}
    //保存一条数据
    boolean saveTask(Task task) {}
    //对数据进行排序
    Data orderData(Data data, int orderType){}
}

首先我们创建一个Model类名为TasksRepository,首先不要关心里面的Data和Task对象,它们只是普通的Bean对象,TasksRepository里面包含了五个方法,按照之前的定义,它是有获取数据的职责的,所以这其中三个方法都是和获取数据相关,比如getTasks,你可以调用网络组件进行数据的获取,它还可以对数据进行CURD的操作,比如savaTask的方法用来保存一条数据,还可以对数据进行业务处理,比如用orderData方法对数据进行重新的排序。

一句话总结,Model负责获取数据,操作数据,和对数据进行业务处理。

什么是View,它的职责

接下来我们讲一讲View,View就是我们的视图层。

1、它的主要职责为呈现Model的数据、主动询问状态或被动的监听 2、通知控制器Controller去处理一些事情 3、接收Controller,编辑自己与Model无关的状态

按照View的职责我们来设计一下这个类

/**我是一个View**/
public class TaskView {
    //当列表初始化后,告诉控制器该加载数据了
    void viewCreate() {
        controller.loadNomData();
    }
    //更新列表
    void upDateList() {
        //主动请求Model获取数据
        Data data = tasksRepository.getTaskCache();
        //更新ui
        list.update(data);
    }
    
    void beginLoadData(){
        list.showHead();
    }
}

在TaskView中也就是我们的视图对象,它包含了俩个对象,分别为controller和taskeRepository,controller就是控制器对象,我们下一个讲它,taskeRepository就是我们的模型,上面提到过它,先不要关心这两个对象是怎么初始化来的,要关心的是View视图对象是包含了C和M的,按照职责它必须这么做,首先像viewCreate这类方法一般是在界面初始化时调用的(在Android 中可能是Activity或者Fragment初始化时调用的,也可能是某一个执行动作),让controller去执行loadNomData的方法请求数据,这里唯一需要注意的是,并没有明确的告诉controller请求的数据是从什么地方来的,也许是缓存,也许是网络请求,view是无需关心这一点的。同时还要注意的是,在讲解Model的时候已经明确了Model是有获取数据的职责,但是在上面的示例中,**为什么是controller去负责获取数据,而不是用Model?也就是我们的tasksRepository对象?**请大家记住这个疑问,会在讲解Controller时回答。

第二个方法upDataList方法,

//更新列表
void upDateList() {
    //主动请求Model获取数据
    Data data = tasksRepository.getTaskCache();
   //更新ui
    list.update(data);
}

它的内部是通过tasksRepository对象的getTaskCache()方法获取数据,这个方法在Model的定义里面,大家还有印象吧。讲到这里,**大家可能发现有一个严重的漏洞,view在执行tasksRepository.getTaskCache()时,是怎么知道这个数据已经准备好了呢?**看view的职责1:

主动的问询或者监听Model

我们把调用tasksRepository.getTaskCache()看作为主动的问询,而在主动问询前,View应该得到一个有效的通知,这个通知应该由Model发起,当监听到Model:我的数据准备好了,你来拿吧时,View会去主动的向Model获取数据。举个现实中的例子,你在网上买东西,现在一般都往快递柜里投放,等到短信通知你快递到了时,你才会去快递柜里拿商品,但也许你实在等不及了,也可以天天打电话,我们就不详细讨论了,View和Model但关系也就是观察者与被观察者的关系。可是,在上面的示例中我们没有写出监听Model的代码,请大家记住这个疑问,我会在后面讲解三者的依赖绑定时会揭晓答案。 最后一个方法,beginLoadData(),

void beginLoadData(){
        list.showHead();
}

当开始请求数据时,list会显示自己的头布局,像beginLoadData这样编辑view本身的方法在实际开发过程中还有很多,它们关注的是控件本身的状态,最重要的,这样的方法可能会被Controller随时的调用,我们在讲解Controller时进行讲解。

什么是Controller,它的职责

Controller,也就是我们的控制器,它把视图的操作发送到模型。

接收View的操作,并转调给Model 改变View的状态

按照上面的职责,我们尝试的设计一下这个类:

/**我是一个Contorller**/
public class TasksController {
    
    void loadNomData() {
        if(tasksRepository.getTaskCache() == null){
            //执行Modle
            tasksRepository.getTasks();
            //执行View
            view.beginLoadData();
        }
    }
}

在对Controller讲解之前,我们先停顿一下,在之前的介绍中我们好像遗留了几个问题,在介绍View时,我用加租文字的方式标示了三个问题,在介绍Controller时我们将要解决掉问题1,和问题3。我们一起回顾一下这2个问题:

疑问1,在讲解Model的时候已经明确了Model是有获取数据的职责,但是在上面的示例中,为什么是controller.loadNomData()去负责获取数据,而不是用Model?也就是我们的tasksRepository对象?

这个问题要从Contrller的职责说起:接收view的操作,并转调给Modle。什么是view的操作,比如list的下拉刷新操作,转调给Model,也就是说其实Controller并没有实际的实现加载数据的代码,而是让Model去执行,解释完以后,看一下TasksController里的第一个方法loadNomData(),

void loadNomData() {
        if(tasksRepository.getTaskCache() == null){
            //执行Modle
            tasksRepository.getTasks();
            //执行View
            view.beginLoadData();
        }
    }

它内部第一条语句:

tasksRepository.getTasks();

而tasksRepositor对象就是我们的Model,它执行了getTasks()的操作,其实controller的loadNomData()方法只是对这个过程进行了一个封装。听完上面的解释,我们知道Controller是这么设计的,可是又一个问题出现了,虽然我们知道了Controller转调了Model的方法,可是我为什么要这么做?我直接在View里调用tasksRepository.getTasks();不可以吗? 清大家记住这个问题,我下一章为大家解答,我们先学会写,再去研究怎么用。

疑问2,像beginLoadData这样编辑View本身状态的方法在实际开发过程中还有很多,它们纯粹关注的是控件本身的状态,最重要的,这样的方法可能会被Controller随时的调用。

我们先回顾一下这个方法beginLoadData()

//controller会随时调用
void beginLoadData(){
        //显示列表的头部,改变了列表的属性
        list.showHead();
}

我们还是从Controller的职责来说,Controller有权利对View的状态进行改变,不管是通知的形式,还是直接的访问。而且View的职责里也说明了,它是可以接收Controller更改自己的状态,而更关键的一点这个状态的改变是无需依赖Model的,更加的纯粹。

有了上面问题的承上启下,我们再回头看一下TasksController里的loadNomData()方法,它首先判断了是否可以从tasksRepository里拿到缓存,如果没有就执行getTasks(),同时通知view我要开始加载数据了,view会在beginLoadData()方法中对自己的列表控件进行一个显示头部的处理,相当规范的一个封装。

**总结:**到此,我们已经学习完了MVC这三个层的定义和职责。在文章的开头我有讲过,要想用MVP,就要先用MVC,要想用MVC就要先会"写"MVC,写才是第一步,我们首先把MVC这三个层,拆分成三个片段,每一个片段都规范好它们的职责,然后我们再想办法把它在组装在一起,在下一章我们要解决以下问题:

1、如何把这三个片段组装起来?在Android里怎么写? 2、view在执行tasksRepository.getTaskCache()时,是怎么知道tasksRepository数据已经准备好了呢?怎么通知view的?

MVP那些事儿(3)……在Android中使用MVC(上)

谢谢大家关注和评论,更新的有些慢,不如关注我一下,随时知道更新进度。