MVP那些事儿 (4) 在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那些事儿(3)……在Android中使用MVC(上)

在上一篇中,我们学习了MVC架构图原理和它的进化过程,并通过is a,has a,依赖注入原则对MVC中三个对象进行组合,同时从无到有的搭建出MVC框架的基本雏形,

灵与骨,血与肉

在上一篇中,我们的MVC框架已经完成了初步的搭建,当然,还不是框架最终形态,虽然三个对象通过某种联系组合了起来,但让框架真正运转起来还需要最关键的一个机制,那就是沟通机制,就好比人类,光有骨架和血肉还不能称之为一个完整的“人”,你还需要神经系统帮助你去看,听,和感受。

沟通机制

在Java的面向对象设计中,监听是一种常用的沟通机制,在观察者模式里,一个监听机制所涉及到的对象包括:监听者(Observer)、被监听者(Obserable);涉及到的环节包括:订阅(Subscribe)、发送事件、及处理事件。

场景

使用以下两个需求作为本章场景:

1、列表展示

2、列表支持下拉刷新,上拉加载更多

实现监听机制

既然监听是一个常用的沟通手段,我们就开始“升级”我们的框架

监听的好处

在开始之前,依旧要用一个场景来描述一下监听的好处,还记得之前租房子的故事吗?在这个故事里,我故意忽略了沟通的机制,就是为了留在这一章节讲的,当租客联系到中介时,这是一个主动的动作,租客是发起方,当和中介建立联系后,他们双方互留电话,同时中介找到合适的房东,并且也留下了联系方式,这个时候中介开始等待房东的回应,这期间中介什么都干不了,一分钟一个电话的询问房东是否考虑好了,那么中介的下场只有两个,房东很生气,一分钟一个电话,你没事儿,我还有事儿呢,你等我消息不行吗?直接拉黑。或者由于中介一次只能处理一个事情,这件事处理不完,就不能处理下一件事,效率低下被公司开除。租客也是一样,一次次的去询问中介,找到房子了吗?等待他的下场也有两个,一、一次次的电话,导致电话费报表,二、由于电话费太贵,打算一天问一次,由于获取消息不及时,结果房子被别人租走了,也就是消息的即时性低,而露宿街头(虽朱门酒肉臭,但别路有冻死骨,愿在外漂泊的你们在这寒冷的冬天里有一个温暖的所在)。

为了避免上面的悲剧发生,中介公司改善了沟通机制,首先从租户的角度,通过主动向租客汇报进度来解决消息即时性的问题,让租户第一时间得到最新情况,其次,中介不再催促房东,而是让房东考虑好后通知中介,当中介收到房东的消息后第一时间通知给租户,通过这两个环节的改造,一条高效的通知链就形成了。

为MVC框架增加监听

Modle的职责是对数据的生产和处理,并在结束一些耗时的操作后,应该主动的通知给Controller,所以Model为被观察对象,而Controller为观察对象,它观察着Model的一举一动,为了能更好的观察Model的行为,Controller派了一个“眼线”到Model中,这个“眼线”的职责就是监听Model的一举一动。

第一步,定义一个“眼线”

/**我是一个“眼线”
public interface Observer {}

这里的眼线就是一个观察对象的接口,但具体让它做什么,我们还不清楚,通过接口的形式未来会有很好的扩展性,定义完眼线,如何使用呢?

还记得上一篇中View是被怎么使用的吗?它被Actvity实现了,也即是说我们这里的“眼线”也应该被某个对象去实现,否则它将没有任何用处,由于是Controller派出了一个“眼线”,所以应该由Controller去使用,使用的两种途径,要么自己具备“眼线”的功能,也就是is a,要么就是自己招募一个“眼线”,Has a。

1、我就是眼线,眼线就是我

/**我是一个Contorller,同时我就是个眼线**/
public class TasksController implements Observer{
    void loadNomData() {}
}

TasksController,通过实现Observer接口,具备了观察者的能力。

2、我招了个眼线

/**我是一个Contorller**/
public class TasksController{
    //我招募了一名眼线
    private Observer observer = new Observer() {};
    void loadNomData() {}
}

TasksController,通过内部实例化了一个Observer接口,间接的获得了观察者的能力。

以上两种都可以获得观察者的能力,但是从扩展性来讲,还是尽量去选择第一种方式。

第二步,放置眼线

有了眼线后,我们还要将它放置在被观察者的内部,这才算完成了观察者与被观察者之间的订阅。

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);
    }
}

这是上一篇内容中的代码段,未来都会围绕着这段代码进行改进,看最下面这一行:

model.setController(controller);

其实,这一步就是model持有了controller,由于我们现在的controller具备了观察者的职责,同时在我们真正的使用中没有必要把整个controller的职责都暴露给model,而model也只需要controller观察者的能力,好让它即时的把结果告知controller,所以我们可以这样改造一下这段代码为:

model.addObserver(observer: controller);

看起来传的参数依旧是controller,只不过改了一个方法名,这没什么区别啊,我想说的是区别还是有的,方法名的改变意味着这段代码的业务变了,虽然都是controller,没改之前是全部的controller,而下面的代码是告诉大家,我只使用controller观察者的部分,其他的我不关心,虽然你全给了我,但用那些是我的事情。

改造过后的Activity:

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.addObserver(observer: controller);
    }
}

第三步,发送事件

这个时候,Model已经获取到了观察者,也就是Controller,那么当Model自己发生变化时,就可以即时的通知给Controller了,我们试着发一个事件,但是在发送事件前,不要忘了眼线还没有具体的能力,我们只是定义了一个接口,眼线具体有什么能力还是要结合具体业务去定义,这不属于架构的部分,更偏向于业务层,这里我们就模拟当Model获取到数据后,通知Controller,我拿到数据了,所以让眼线有通知数据ok的功能:

/**我是一个“眼线”
public interface Observer {
    //数据OK
    void onDataComplate(Data data);
}

目前眼线已经准备完毕,就等着Model来使用了,我们用Model来发送一个事件

Model :TasksRepository

    /**我是一个Model**/
    public class TasksRepository {
        //眼线集中营
        public static ArrayList<Observer> observers = 
            new ArrayList<Observer>();
        viod addObserver(Observer observer){
            observers.add(observer);
        }
        //从服务器请求获取数据
        void getTasks() {
            //访问服务器,耗时。。。服务器返回时,
            Data data = fromServer();
            //发送事件
            for(Observer observer : observers){
                observer.onDataComplate(data);
            }
        }
        //从内存缓存获取数据
        Data getTaskCache() {}
        //从磁盘缓存获取数据
        Data getTaskDiskCache(){}
        //保存一条数据
        boolean saveTask(Task task) {}
        //对数据进行排序
        Data orderData(Data data, int orderType){}
    }

在实际的开发中,Model可不是只为了某一个Controller去监听的,它可以被任何想要监听它的人监听,你只要送一个眼线过来,当Modle有变动时,Model会通知所有关心它的人,所以Model里面有一个Observer的集合:

public ArrayList<Observer> observers = 
            new ArrayList<Observer>();

当Model发生了变化,就会遍历这个集合去通知所有的观察者,而眼线在这里派上了用场

for(Observer observer : observers){
    observer.onDataComplate(data);
}

第四步,接收事件

处理事件的特性是观察者的本质,Controller既然是观察者,那么处理事件应该由自己去完成:

Controller :TasksController

/**我是一个Contorller**/
public class TasksController implements Observer{
    //接收事件
    void onDataComplate(Data data) {
        //处理事件
    }
    void loadNomData() {}
}

TasksController实现了Observer的onDataComplate方法,当Model发送事件后,onDataComplate方法便能接收到,我们就可以在这里处理事件了,到此为止整个事件从创建到处理就完成了,这也就是观察者模式的核心,如果以后需要自己实现一个观察者模式,那么就按照上面四个步骤来写,绝对不会懵圈而且思路会异常的清晰。

那么View呢?

上面的场景提到过,当房东考虑好后通知给中介,中介会第一时间通知租客结果,那么具体改如何做呢?

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.addObserver(observer: controller);
    }
}

回过头来看Acivity的代码中的这一段:

//初始化Controller,this就是View,通过构造器注入
TasksController controller = 
            new TasksController(tasksView:this);

首先,通过构造函数,让controller持有view,当controller接收到model的通知时,紧接着通知view,所以TasksController的代码还需改进:

/**我是一个Contorller**/
public class TasksController implements Observer{
    //通过构造函数接收view
    public TasksController(TasksView view) {
        this.view = view;
    }
    //接收事件
    void onDataComplate(Data data) {
        //处理事件,紧接着向view发送事件
        view.onDataBack(data);
    }
    void loadNomData() {}
}

我们看处理事件的部分,直接执行了view的方法,也就是所谓的即刻通知。

让View也接口化

按早之前Controller观察者化的思路,我们能不能让view也变成观察者,当然可以而且是必须的,让view 去观察Controller的变化,Controller又去观察Model的变化,那么整个链式反应就完成了。具体步骤就不分析了,上一个完整的代码:

View :TasksView

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

Activity:

public class MainActivity
extends AppCompatActivity
implments TasksView{
    private TasksController controller;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Controller,this就是View,通过构造器注入
        controller = 
            new TasksController(tasksView:this);
        
        //初始化Model,Model -> View and View -> Model
        TasksRepository model = 
            new TasksRepository(tasksView:this);
        
        //Controller -> Model
        model.addObserver(observer: controller);
        viewCreate();
    }
    //接收controller的事件,并处理
    void onDataBack(Data){
        //处理事件。。。
    }
    //当列表初始化后,告诉控制器该加载数据了
    void viewCreate(){
        controller.loadNomData();
    }
    //更新列表
    void upDateList(){}
    //just for ui
    void beginLoadData(){}
}

总结:

这一篇中,我们通过观察者模式对我们的框架进行了改进,通过监听,让MVC的三个对象形成了一个事件传送带,事件就好比有了方向一般从Model出发,经过Controller最终流向View,而后期我们可以在这条链路上对我们的事件做任何想要做的操作,而最终的接收者View是完全不用关心的,亦或者view可以自定义自己想要的数据,在Model还没有发送事件前。说的更确切点,我们可以在事件的发送前,传输中,接收前,这三个点做很多我们希望做的事情,比如数据在接收前的一些排序的转变,这些我们都会以接口的 方式暴露出来。到此,MVC的介绍结束,但框架的搭建还没有完成,在接下来的被容里,我们通过MVP的方式对框架进行进一步的改进,同时加入一些实质些的工具,让框架具备一些基本的业务功能。

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