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

为什么要先介绍MVC?

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

目录

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

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

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

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

MVP那些事儿(5)……中介者模式与MVP的关系【知识点】

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

MVP那些事儿(7)……Kotlin实现MVP【知识点】

MVP那些事儿(8)……当MVP遇到Lifecycle【知识点】

MVP那些事儿(9)……探究MVP的最佳实践

MVP那些事儿(10)……MVVM双向绑定

MVP那些事儿(11)……基于MVVM的Architecture Components

我们在上一章提到,MVP那些事儿(2) 用场景说话 对我们的案例进行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大家都在用,自己设计一个吧,到最后发现根本就你一个人在吃菜,更关键的是别人想和你吃也吃不了,不通用,根本无法体现它的扩展价值,负责人发现后第一反应是,你搞这么多接口抽象类干什么?就你一个人用,搞事情。


image

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(上)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270