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

目录

MVP那些事儿(1)……用场景说话设计模式

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

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

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

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

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

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

几天前Google IO大会刚刚落下帷幕,相信这又会在技术圈里掀起一阵浪潮,不得不说,Google对Android的热情未曾消减,这对咱们来讲但是一如既往的暖心,毕竟这颗大树养育了很多产业,废话很少说,带着这股暖意咱们开启本章的内容。框架

在此以前,感谢一下读者,在第四章节文章里找出了一处笔误,证实你们认真的去思考了,在此真心感谢,同时错误已经在评论区里纠正。ide

在开始以前,仍是但愿你们能阅读一下以前的文章,虽然每一章都有一个重点,但这终究是一个体系的学习,在上一章MVP那些事儿(6)……MVC变身为MVP里,已经为你们讲解了MVP整个框架的搭建思路,那么在这一章中,让它变的丰满一些,让它有一些实际的用途。工具

目前主流的网络层实现是基于RxJava和Retrofit,你们估计已经耳根子都被这俩个家伙磨破了,放心,这里我不会讲解他们的具体使用,那么在咱们开发中,网络请求是一个很是重要的环节,在不使用框架时,咱们依旧可使用Retrofit快速的开发出网络请求的业务代码,而咱们一旦使用了框架,就要考虑,如何将它们这些工具集成到咱们的框架中,这可不是简单的引用和封装,本章会介绍如何设计一个通用的Repository。

Repository的设计

在Google官方的示例中,Model层被具象成为Repository仓库,相信不少小伙伴都有看过todo的Demo,包括我以前的文章,命名方式也是沿用了google,这样你们在理解类的含义的学习成本会变的很低,而更多的精力去关注文章自己所要表达的内容,因此在这里咱们也用Google的思想去设计Model层和它的实现:

咱们经过一个类图来表示:

知识点:装饰设计模式,赋能与递归

很显然,Repository的设计采用了装饰设计模式,对于装饰设计模式,它的关键词就是“赋能”,(这词最近很流行)但这个赋能并非对本体直接进行赋能,而是创造出来一个新的外壳,这个外壳获取到本体的能力后,能够对本体能力进行演化,而这个外壳也会把演化后的能力对外暴露,让其余外壳对本身的能力进行演化,还有一种状况,外壳并非演化本体功能,而是又附加了一个全新的功能,想象一下你的手机加了一个保护壳,又加了一个外接电源(对原有电量扩充),又加了一个手柄壳(全新功能)。本体能够有多个,而基于本体的外壳能够有多个,而一个外壳再其余的外壳眼中也多是个本体,想一想都很酷炫,而这样的既能纵向又可横向的扩展要比传统的单继承和多实现来的舒服的多。关于递归,装饰模式自己也是类递归的体现,这里不详细阐述。

在Android的开发中咱们必定会去服务器获取数据和资源,再结合实际的须要是否须要把这些数据缓存在本地,或者对本地数据进行一些操做,而这些功能的统一出口就是Repository,因此前期设计只需考虑这两个方向,本地和远程,别忘了因为使用的是装饰设计模式,在将来咱们能够根据具体业务来建立出他们的演化版本,而无需污染本体,咱们彻底能够独立这块业务,而使用Repository的用户,能够彻底不用关心这些,这其实也是分层架构的魅力。接下来咱们经过一个简单的示例去讲解这部份内容。

具体设计与实现,让咱们来设计一个Repository

首先,咱们定义一个DataSource接口,它只有一个职责getObject(),DataSource 能够看做为顶级接口,它是一切的起源(始祖)。

一、定义DataSource

public interface DataSource {
    void getOject();
}
复制代码

基于这个始祖咱们须要定义两个子类,Remote和Local,咱们先忽略他们的getObject()的具体实现(涉及到具体实现可能会干扰对设计理解),你能够简单的想象一下,一个是从远端获取数据, 一个是从本地获取数据:

二、实现一个RemoteDataSource和LocalDataSource

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //具体实现
    }
}
/**
*   Local
**/
public LocalDataSource  implements DataSource{
    void getInstance(){}
    void getOject() {
        //具体实现
    }
}
复制代码

按照设计,Repository的总体设计思路主要是装饰模式,而目前为止只看到了实现,并无所谓的“赋能”,因此咱们这里还缺失一个主要的角色Repository,它如何去设计?

对外提供统一接口

咱们在开发过程当中,常常能听到这样的建议:请对外提供一个统一的接口,这样之后有改动只须要改动内部逻辑,外部引用就不用改了,同时考虑到具体业务:数据的获取能够从远到或者是本地获取,那么何时从远端获取?何时从本地获取?拿去集成在业务代码中的开发兄弟会去关心吗?他们只关心,若是这块的业务逻辑变了,他们要不要修改代码?这个时候你会很负责的告诉他们,大家无需关心,由于我提供了一个统一接口Repository,业务层开发的兄弟大家只要关心在什么时机去初始化和调用就好。

三、实现Repository

按照上面的分析,一、让Repository继承始祖DataSource,二、让Repository依赖于 RemoteDataSource和LocalDataSource:

public class Repository implements DataSource{

    private LocalDataSource local;
    private RemoteDataSource remote;
    
    void getInstance(DataSource remote, DataSourc, local){
        this.remote = remote;
        this.local = local;
    }
    
    @Override
    void getOject() {
        if(//逻辑判断) {
            local.getObject();
        } else {
            remote.getObject();
        }
    }
}
复制代码

首先Repository也是一个DataSource,它一样拥有getOject()的能力,而它对getOject()方法的处理,实际上是借用了local和remote的能力,换个角度来看Repository就是remote和local的包装类,它作的只是实现了某部分业务逻辑:即,何时调用local和何时调用remote。因为Repository是对外统一接口对象,因此当内部业务逻辑发生改变,外部是无需关心的。

为了Repository使用方便,咱们建立一个静态工厂类RepositoryInjection

public class RepositoryInjection {

    public static Repository provideRepository(@NonNull Context context) {
        checkNotNull(context);
        return Repository.getInstance(RemoteDataSource.getInstance(context),
                LocalDataSource.getInstance(context));
    }
}
复制代码

那么业务开发的同窗在合适的地方能够这样调用:

RepositoryInjection.provideRepository(context).getObject();
复制代码

此时这个getObject方法究竟是remote的仍是local的,亦或者是阿猫阿狗的,对于业务来讲无需关心。

经过对Repository的分析与实现,咱们了解了在特定的业务场景下使用装饰的好处,那么为了加深理解,咱们再假设一个场景,尝试着去扩展一下。

案例场景描述

假设咱们的remote已经知足了基本的网络请求功能,并正式投入到线上,这个时候安所有门报了一个漏洞,说咱们的加密key在项目里使用了明文硬编码,可能会被获取,这个时候为了紧急修复,咱们选择经过接口动态获取安全key来增长安全级别,也就是说在请求全部的数据前都须要先请求一下key,这个简单的需求咱们能够直接改动remote的逻辑以下:

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //一、先获取key
        getKey();
        //二、再获取数据
        getData()
    }
}
复制代码

你很快的完成了这个需求并沾沾自喜,上线运行,过几天技术经理发现了一个问题,因为接口访问量太大,资源消耗严重,决定只对部分接口进行加密,这个时候remote可能没法独立去完成这个需求,就须要扩展了,如何扩展?咱们能够这样:

首先,把RemoteDataSource改成最初的版本。

/**
*   Remote
**/
public RemoteDataSource  implements DataSource{
    void getInstance(){}
    void getOject(){
        //获取数据
        getData()
    }
}
复制代码

其次,定义一个RemoteKeyDataSource类对RemoteDataSource进行包装

/**
*   New Remote key
**/
public RemoteKeyDataSource  implements DataSource{
    private RemoteDataSource remote;
    void getInstance(DataSource remote){
        this.remote = remote;
    }
    void getOject(){
        //获取key
        getKey();
        //获取数据
        remote.getObject();
    }
}
复制代码

咱们演化的getObject方法首先执行getKey()获取加密key,而后执行remote的getObject()方法,这样在不修改remote的前提下功能获得了演化。

How to use

修改一下Repository

public class Repository implements DataSource{

    private LocalDataSource local;
    private RemoteDataSource remote;
    
    void getInstance(DataSource remote, DataSourc, local){
        this.remote = remote;
        this.local = local;
    }
    
    @Override
    void getOject() {
        if(//逻辑判断) {
            local.getObject();
        } else if(//须要key){
            //包装一下remote
            RemoteKeyDataSource.getInstance(remote).getObject();
        } eles {
            //不须要要key
            remote.getObject();
        }
    }
}
复制代码

而对于业务方的同窗,他们对此次修改是“无感知”的。若是有一天业务场景发生了变化要用回RemoteDataSource,相信那个时候你必定会从容不迫。

总结

在真实的业务场景中,功能扩展是永远不变的话题,其实模式很好理解,设计也很好去揣摩,但如何去合理的拆解功能是须要大量的经验去堆砌的,咱们经过装饰模式来尝试去分析Repository的设计核心思想,合理的功能划分加上正确的拓展思路是必不可少的,但愿经过这章内容给你们作一个抛砖引玉的做用,设计就是这样,没有固定的范式,但必定要掌握其本质。

在Android中也有装饰模式的身影,好比ContextWrapper对Context的包装。那么Repository设计就仅限于此吗?答案是否认的,一个通用的框架可考虑的点仍是不少的,但愿能在将来的章节中有机会就Repository的设计增长几个场景。

相关文章
相关标签/搜索