Android 依赖注入详解

01.什么是依赖注入

  • 有哪些注入的方式可以解耦
    • 举个例子,假如我们的Presenter层需要用网络的数据或数据库的数据,但presenter本身没有请求网络的功能,那么它要依赖model层去请求网络,所以它就需要持有model层的一个实例,我们可以直接通过new的方式去创建,但是这样会使代码耦合严重,那么我们就可以通过一些解耦的手段去让presenter层持有model的引用,比如通过构造方法注入、set方法注入、配置文件注入、注解注入等等,只要达到我们的目的就是好的。
  • 什么是依赖注入
    • 在面向对象编程中,经常处理处理的问题就是解耦,程序的耦合性越低表明这个程序的可读性以及可维护性越高。控制反转(Inversion of Control或IoC)就是常用的面向对象编程的设计原则,使用这个原则我们可以降低耦合性。其中依赖注入是控制反转最常用的实现。

02.依赖注入案例

  • 依赖是程序中常见的现象,比如类Car中用到了GasEnergy类的实例energy,通常的做法就是在Car类中显式地创建GasEnergy类的实例,并赋值给energy。如下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface Energy {

    }

    class GasEnergy implements Energy {

    }

    class Car {
    Energy energy = new GasEnergy();
    }
  • 存在问题

    • 类Car承担了多余的责任,负责energy对象的创建,这必然存在了严重的耦合性。举一个现实中的例子,一辆汽车使用哪种能源不是由汽车来决定,而是由汽车制造商(CarMaker)来决定,这是汽车制造商的责任。
    • 可扩展性,假设我们想修改能源为电动力,那么我们必然要修改Car这个类,明显不符合开放闭合原则。
    • 不利于单元测试。
  • 有哪些方式解耦

    • 依赖注入是这样的一种行为,在类Car中不主动创建GasEnergy的对象,而是通过外部传入GasEnergy对象形式来设置依赖。 常用的依赖注入有如下三种方式
  • 构造器注入

    • 将需要的依赖作为构造方法的参数传递完成依赖注入。
    1
    2
    3
    4
    5
    6
    class Car {
    Energy mEnergy;
    public Car(Energy energy) {
    mEnergy = energy;
    }
    }
  • Setter方法注入

    • 增加setter方法,参数为需要注入的依赖亦可完成依赖注入。
    1
    2
    3
    4
    5
    6
    7
    class Car {
    Energy mEnergy;

    public void setEnergy(Energy energy) {
    mEnergy = energy;
    }
    }
  • 接口注入

    • 接口注入,闻其名不言而喻,就是为依赖注入创建一套接口,依赖作为参数传入,通过调用统一的接口完成对具体实现的依赖注入。
    • 接口注入和setter方法注入类似,不同的是接口注入使用了统一的方法来完成注入,而setter方法注入的方法名称相对比较随意。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface EnergyConsumerInterface {
    public void setEnergy(Energy energy);
    }

    class Car implements EnergyConsumerInterface {
    Energy mEnergy;

    public void setEnergy(Energy energy) {
    mEnergy = energy;
    }
    }

03.依赖查找和依赖注入

  • 依赖查找和依赖注入一样属于控制反转原则的具体实现,不同于依赖注入的被动接受,依赖查找这是主动请求,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。

04.路由框架为何需要依赖注入

  • 路由的目的就是要实现跳转,但是你有没有想过,两个Activity之间的跳转肯定免不了要传入一些参数,如果我们在跳转后还要通过intent去获取参数,这样岂不是很麻烦,如果可以自动把参数赋给属性多好啊!
  • 组件化中两个module之间可能有一些功能并不需要跳转页面,如支付模块要获取用户模块的用户id,并不需要跳转页面,那么我们就要持有用户模块含有获取用户id功能的类的引用,如果我们在支付模块创建一个用户模块的功能引用,显然就违背了解耦的规则。这两个问题显然用依赖注入的方式会更好些,如果你用过ARouter,你会发现ARouter中的服务(IProvider)就是通过依赖注入实现的。

05.路由用什么方式注入

  • 可以使用注解的方式,至于为啥,接下来我分析一下……
    • 为什么要用注解实现依赖注入,因为我们用了apt啊,那岂不是天生实现依赖注入的利器。如果你去写配置文件或构造方法等等,未免太复杂。
  • 无法通过构造方法注入【构造器注入】
    • 在多组件并行开发过程中,因为两个module没有引用关系,所以就不能通过构造方法传入要依赖的类,这个时候怎么办呢?连对方的引用都得不到,如何进行依赖呢?
  • 不要通过反射方式注入
    • 可能你会想到反射,我们先pass这个方案,能不用反射就能做好的前提下,我们最好不要用反射。
  • 不建议通过接口方式注入【也可以,但若是开源成lib,则不建议】
    • 可能有人会想到,在基类下沉一个接口功能标准,比如在基类库base模块定义获取用户id的接口,然后在用户模块实现接口的方法。那么当支付模块需要用到这个功能,就声明这个接口,然后一行注解通过框架为你创建实例,这样用户模块只需要提供功能,并不需要关心谁在用这个功能,这样岂不是大大减小了耦合。
    • 但是有一点,组件化实践中,有很多个模块,那岂不是所有的都需要依赖base基类库,才能使用到接口注入,这样不易转接。