MVP那些事儿 (3)……在Android中使用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三个层的基本职责,我们再回顾一下这三个“片段”:

Model层

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

View层

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

Controller

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

同时还遗留了两个问题:

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

把片段“组合”起来

在模式设计时,我们会通过关系图来反映对象间的关系,如下图:

MVC架构图

这是一张非常经典的MVC架构图(手残,有点丑……),相信大家对这张图已经不陌生了,OK,这张图我先放下不表,因为考虑到我们介绍的是架构,本身是抽象的,加上如此抽象的图,怕是不能好好的切入,所以我想尽可能的巨像化这个过程,当大家有一个清晰的理解后,再回过头来重新审视它。我本来是想放在文章的后半段祭出这张图的,但想了想有点不妥,原因是我们上期已经通过一整篇用来初探MVC,也该把MVC的架构图贴出来应一应景了……

image

那么MVC这三者的关系究竟是怎样的呢?

我们依旧通过现实中的场景来描述一下MVC三者的关系,让大家对这三者的关系有一个初期的直观的理解,起到一个抛砖引玉的效果,废话不多,场景开始。

场景

想必大家都租过房子,有当租客的体验,也有一部分人可能还有当过过房东经验,在当下的社会中,房屋中介是我们租房找房必不可少的环节,我相信大部分人都和中介打过交道,咳咳,那我们就还原一下租房的过程,

首先,确定角色

在这个场景里包含了三个角色,租客、中介、和房东,

其次,选择角度

我们从租户的角度描述一下租房的场景,只考虑正向流程。

再次,场景描述

租客要先找到中介并描述自己需要的房子,中介接到需求后,通过筛选发现有一个房东很适合,于是中介开始与这个房东接触,询问一些和房子租金相关的事宜,如果中介觉的合适,他会把反馈告知客户,如果客户也觉的合适,中介就会把租客和房东约在店里进行更进一步的协商,如果能促成合同,那么到此为止中介完成了现阶段的工作,剩下的事情,比如租客向房东每个季度缴纳房租,或者询问房东家里电器的使用方式,亦或者房东询问房客租约到期是否续租等等,这些沟通都可以抛开中介去完成,也就是说,不一定需要中介作为他们之间沟通的桥梁,但也并不意味着中介在以后的场景中是不可用的,比如,在租房过程中有一些事情租客和房东有分歧,也可以通过中介来进行协调和沟通。

还有一种中介的形式是,租客和房东是见不到彼此的,全程由中介负责,这种房叫托管房,租客不知道房东是谁,房东也不知道租客是谁,合同都和中介签,我们现在不讨论这种情况。

来个图描述一下上诉的情景

好随便的图

这张图的目的一来是复述一下上面的场景描述,二来是让大家直观的看到它们三者交互的一个关系。

中介、租客、房东,把他们和MVC的三个角色关联起来

通过上诉场景的描述,和前一篇帖子中介绍的MVC各个对象的职责,想必他们到底谁是谁一目了然,我们试着关联一下这几个对象

首先看中介,他负责了执行租客的招租要求,并告知房东这些要求,也就是说他作为中介向房东转告租客的需求,那中介就是我们的Controller。

再次看租客,他是需求的发出者,他比其他几个对象更积极的提出需求,就好比View,总是再向Controller提出需求,也会更主动的向Model询问状态,就像租客一样,遇到问题,要么找中介,要么去询问房东,所以,在这里把租客看作是View更加的贴切。

最后只剩下房东这个角色了,最后的Model就分配给房东吧。

接下来我们用MVC的方式来梳理一下中介、租客、房东的关系

1、View To Controller 求租房


image

2、Controller To Model 询问:出租吗?


image

3、Controller To View 告知:找到房源
image

4、View To Model 单聊:能便宜吗?


image

5、Model To View 单聊:没门儿!
image

上面这五张图,是把MVC三个对象间的关系,以及案例中的业务流程做了一个“融合”,是一个抽象到具象的“融合”,架构图中这些带箭头的线,是用来解释对象间关系的,无非就是那些谁依赖谁的解释,我同时又在线上加入了一些事件,是希望能把上面具象的问题引到抽象的架构中来,在实际中,这样的融合是不符合常理的,比如你无法用单个业务场景去定义对象间的依赖关系,比如View To Controller,难道只有求租房这个场景才可以这么用吗?显然不是的,包括各个角色中括号后面的称呼。所以,为了解决通用性的问题,人们把这样的具象问题抽象成了一种依赖关系。

架构是蓝图,抽象的存在,而框架是具象的,它是为了解决具体某一类问题而设计的。

MVC架构图是怎么来的?抛去具象,看本质

具象到抽象三连图

抽象到最后就是我们开篇的第一张图。

在某些场合里,需要我们去介绍我们的软件架构,这就需要我们具备描述架构的能力,我觉的可以先描述一下具象的问题,再描述向上抽离的步骤,最后陈述抽象的产物,也就是我们的架构,是一个不错的思路。

应用到实际中

通过上面大篇幅的抛砖引玉,相信大家对MVC的架构有了一个初步的认识,那么接下来我们开始真正的使用,还记得上一篇我们使用了一个需求:

需求:列表加载数据并且展示

我们依旧使用这个需求,因为前面定义好了三个层的类,接下来要做的是按照MVC的架构把他们拼装起来:

在Android中使用

开始加入到实际开发阶段,我们先创建一个Activity

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

紧接着,我们把上一篇定义好的三个MVC对象也加入进来

Model :TasksRepository

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

View :TasksView

    /**我是一个View**/
    public class TaskView {
        //当列表初始化后,告诉控制器该加载数据了
        void viewCreate() {}
        //更新列表
        void upDateList() {}
        //just for ui
        void beginLoadData(){}
    }

Controller :TasksController

/**我是一个Contorller**/
public class TasksController {
    void loadNomData() {}
}

现在我们有了Activity,TasksRepository、TasksView、TasksController

有了这些对象,我们就可以进行组合了,我们从什么地方下手呢?也许我们可以从MVC的架构图中得到些启示,虽然架构图很简单,但是有箭头,有方向,在UML中,这些线和箭头便是记录着对象间的关系:

image

按照架构图的提示,我们为TasksRepository、TasksView、TasksController这三个类梳理了一个大致的依赖关系,同时MainActivity被撂到了一边,并不是它和这三个对象没有关系,而是它现在还不能直接用,它需要变一下,为什么这么说呢,来看例子:

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化MVC
        TasksController controller = new TasksController();
        TasksView view = new TasksView();
        TasksRepository model = new TasksRepository();
        //依赖关系
        controller.setView(view);
        view.setController(controller);
        view.setModel(model);
        model.setController(controller);
        model.setView(view)
        //执行业务
        controller.loadNomData();
    }
}

我承认,我又开始抛砖引玉了,从这种写法来看,Activity到是像MVC的容器,它包含了MVC这三个对象,这么写有问题吗?在继续之前,引用一个概念,Is a…… and Has a

Is a 与 Has a

翻译过来,就是,我是什么,和我有什么

Is a 我是什么?

//我是什么动物?我是一只猫
class Animal implments Cat {
    //喵喵叫
    void miaomiao();
}

Has a 我有什么?

//我有什么动物?我有一只猫,一只狗,一只豹子
class Animal{
    public Animal(Cat cat) {
        //来只猫
        this.cat = cat;
        //来个豹子
        this.baozi = new Baozi();
    }
    //来只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
}

相信大家看出了其中的差别,回过头我们看一下MainActivity,它是什么,它又有什么,首先它是一个Activity,其次,它有TasksRepository、TasksView、TasksController这三个对象,重点在Has a上,像这样的依赖耦合度是非常高的,基本没有扩展的余地,为了降低耦合,可不可以让MainActivity少几个Has a?当然可以。

我是一只猫,我有一只狗和豹子

//我是什么动物?我是一只猫
class Animal implments Cat {
    //喵喵叫
    void miaomiao();
    public Animal() {
        //来个豹子
        this.baozi = new Baozi();
    }
    //来只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
}

所以我们让MainActivity变一下身,从MVC这三个对象TasksRepository、TasksView、TasksControlle中选一个变,这样强耦合的个数从3个变成了2个,那么选谁好呢?
目前主流的是选择View,和选择Controller,大家都是从业务的角度出发去选择,首先选择View,因为Activity里包含了布局对象,所以因为“离得近”让Activity变成View,选择Controller的是因为Activity本身的生命周期可以很好的用来控制业务流程,这样对Controller来说更加有利,说实话,我并没有深挖其中哪一个更好,因为我觉的这两种方案都有他们的侧重,还是要从实际的业务角度去考虑问题,那我们突发奇想让Activity既是View,又是Controller呢?这不就解决变成谁的问题了吗?个人觉的还是不要的好,你总不能介绍自己的时候说:大家好,我是猫,也是狗,我是男的也是女的,我相信到时候场面一定很混乱,我们还是保持类的职责单一性吧。但没准那天变成Model玩一下也可以的吧,所以说,目前我先从变成View来开始,后续的章节我们会讲解变成Controller该怎么办。

更新UML


image

同时更新一下代码,我们让MainActivity实现一个TasksView,赋予它View的职责

public class MainActivity extends AppCompatActivity implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //略……
    }
}

确定了Activity,我们才开始了第一步,接下来我们就要看一下Controller的初始化,在此之间需要引用一个概念,依赖注入。

依赖注入

什么是依赖注入?首先解释一下什么是依赖,看下面的代码:

class Animal{
    public Animal(Cat cat) {
        //来只猫
        this.cat = cat;
        //来个豹子
        this.baozi = new Baozi();
    }
    //来只狗
    void setDog(Dog dog) {
        this.dag = dog;
    }
    //猫猫叫一下
    void miaomiao() {
        cat.miaomiao();
    }
    //豹子叫一下
    void aoao() {
        baozi.called();
    }
}

aoao这个方法内部,调用了baozi这个对象叫的方法called(),也就是说,首先Animal这个对象自己是不会叫的,他需要依赖于Baozi对象,Animal依赖Baozi去叫,这个依赖就是Baozi,我们看到Baozi对象的初始化,是在Animal构造器中new出来的。再看一下miaomiao方法内部,依旧是依赖于cat的miaomiao方法去叫,而这个cat对象是从构造函数传进来的,baozi和cat同样是Animal的依赖,但获得的途径不同,cat的获得途径就是注入,而它又是依赖,所以我们称之为依赖注入,依赖注入的形式除了通过构造器参数外,还可以通过普通方法传入,如上面的setDog方法,我们注入了一只Dog。

依赖注入的好处

说到依赖注入的好处,不得不提Java的面向对象的特性中的多态,举个例子,上面的代码中的Cat可以是一个接口,我们在使用Animal类前,可以确定一下Cat的实现类,根据业务需求,接收到的可能是公猫,也可能是母猫,可能是美短,也可能是英短(猫的品种),如果我们把Cat的依赖放在Animal中去初始化,那么根据业务的变化,可能需要频繁的更改Animal类。第二点好处,当Cat的构造器发生变化,Animal也需要更改Cat,如果Cat需要另外的依赖,比如猫需要猫粮它才能活下去,那么Animal也要间接的依赖猫粮,对象间的耦合性就暴露了出来,所以从外部传入的依赖,Animal无需关心依赖的依赖。

看来依赖注入是非常好的设计手段,那么回到上面的问题,Contorller的初始化,我们知道Controller是依赖于View的,所以我们需要把View注入到Controller中去。

public class MainActivity extends AppCompatActivity implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通过构造器注入
        TasksController controller = new TasksController(tasksView:this);
    }
}

通过依赖注入,我们把Controller和View的命运绑定在了一起。
说完了View -> Controller,我们还要解决Controller -> View,其实我们可以发现,Controller就是包含在View中的,因为Activity在上面已经变身成了View,而Controller也在Activity中通过new的方式进行了初始化,也就顺理成章的成了View的依赖了,只不过它不是通过注入的方式进入到View(MainActivity)中的,那么有什么办法可以做到这一点呢?当然有办法了,介绍一下依赖注入的第三种方式:通过构建一个Controller的工厂来负责生产Controller的实例,具体就不在这里细说了,以后讲到Dagger时可以和大家详细讨论。

Model的初始化

那么Model(TasksRepository)的初始化放在哪里去做呢?以及它与Controller和View的关系又是怎样的呢?从MVC的架构图和框架的UML图里可以得到答案,Controller和Model之间是一个单向的依赖,也就是Controller -> Model,同时Model与View是一个双向的依赖,

1、Controller -> Model
2、Model -> View
3、View -> Model

我们只需满足这三个条件,我们直接这样,看代码:

public class MainActivity extends AppCompatActivity implments TasksView{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通过构造器注入
        TasksController controller = new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.setController(controller);
    }
}

首先在Activty中,初始化Model,并将View注入到Model里,这样,同时满足了Model -> View 和 View -> Model,其次,通过model的setController方法将上面创建好的Controller注入到Model中,也就是满足了Controller -> View 这个条件。

阶段性总结

到此我们就完成了MVC片段的组合,我们从头再梳理一下这个过程

实现步骤:

1、Is View,Activity实现View

将Activity赋予View的职责。

2、Controller -> View ,在Activity中创建Controller的实例。

通知控制器Controller去处理一些事情
接收View的操作,并转调给Model

3、View -> Controller,将View注入到Controller。

接收Controller,编辑自己与Model无关的状态

4、Model -> View,在Activity中创建Model的实例。

主动询问Model的状态,呈现Model的数据

5、View -> Model,将View注入到Model。

监听Model的变化,如有变化通知View

6、Controller -> Model,将Contoller注入到View

监听Model的变化,如果变化通知Controller

心路历程

通过MVC的架构图,我们尝试的设计了UML,并且通过Is a ,Has a原则确定了View的归属,其次,通过依赖注入原则,确定了依赖创建方式,再次,通过MVC的架构图与UML确立了Controller,View, Model这三者之间的关系,再结合依赖注入原则完成了整个框架的搭建。

总结

在之前的介绍MVC各个层的职责中,“监听”这个词出现的频率很高,在面向对象语言的设计中,“监听”是一个动作,与它相反的便是“获取”这个动作了。目前这个框架还是个雏形,并没有“监听”的能力,同时包括各个层代码的不完善,以及控制反转等实际性内容的缺失…… 本来想用一章来写完,但篇幅有些长,看来不得不分了。以前总觉的看别人的帖子挺快的,不一会儿就看完了,但真正自己去码可是真的慢,主要是担心描述的不够清晰,担心不能把自己的想法很清晰的表达出来,同时又担心过于清晰的表达会不会又显得啰里八嗦,删了改,改了删的,总之,我会尽最大努力完成这个系列的,喜欢的就点个赞,鼓励鼓励我。

最后的最后,布置一个家庭作业,看下面三张图:

image

问题1,图一和图二的区别,以及图二算不算MVC
问题2、MVC可以像图三那样去设计吗?

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