2. Use's guide

0x00、 用户引导

原文

在任何应用中被评价为“好”的类都是做事情的类:BarcodeDecoder(条码解析)、KoopaPhysicsEngine(Koopa物理引擎)、AudioStreamer(音频流)。 这些类有其依赖的东西;或许是BarcodeCameraFinder(条码相机查找器), DefaultPhysicsEngine(默认物理引擎), and an HttpStreamer(Http流)。

作为对比,在应用中被评价为“差”的类都是没有做太多事情却占用控件的类:BarcodeDecoderFactory(条码解析器工厂)、CameraServiceLoader(相机服务加载器)、MutableContextWrapper(可变上下文包)。 这些笨重的‘导管/胶带’将这些有用的东西拼装在一起。

Dagger 是类似 FactoryFactory 类的替代品,它实现 依赖注入 设计模式没有编写样板文件的负担。 它使你专注于核心类。 声明依赖,指定如何满足它们,以及如何装再你的 app 。

通过构建标准(JSR 330)的 javax.inject 注解,每一个类都易于测试。 你不需要用一堆模板将 RpcCreditCardService 换成 FakeCreditCardService 。

依赖注入不仅用于测试。 它也被用于创建可复用的通用模块。 你能夸应用共享相同的 AuthenticationModule 。 并且,你能在开发过程中运行 DevLoggingModule 在线上版本运行 ProdLoggingModule 从而在不同情况下使用合适行为。

0x01、 为什么 Dagger 2 是特别的

依赖注入框架已经存在了很多年,有多种用于配置和注入的 API 。 所以,为什么造一个新轮子呢? Dagger 2 是第一个实现了生成全栈代码的框架。 它的指导原则是模仿使用者的手写的代码生成代码从而确保依赖注入的简约、可追踪和高效。 关于更多设计性的问题请参阅链接-1链接-2链接-3 + Gregory Kick

0x02、 使用 Dagger

我们将通过构建 coffee maker 演示 Dagger 和 依赖注入。 完整的示例代码:coffee example

2.1、 声明依赖

Dagger 构建应用中类的实例,进而满足它们内在的依赖关系。 它使用 java.inject.Inject 注解来标记它感兴趣的 构造函数 和 字段 。

Dagger 应该使用被 @Inject 注解的构造函数创建类实例。 当一个新的实例被请求的时候,Dagger 将获取请求参数并且调用这个构造函数。

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger 能直接注入 字段 。 在这个例子中它获取了一个 Heater 类型的 heater 实例和一个 Pump 类型的 pump 实例。

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;

  ...
}

如果你的类使用了 @Inject 注解字段,但是没有 @Inject 注解 构造函数,在被注解的字段被请求的时候 Dagger 将注入这些字段,但是这将不会创建新的实例。 比较好的做法是创建一个被 @Inject 注解的 无参 构造函数。

Dagger 也支持方法注入,构造方法或者字段注入通常是首选。

类中没有 @Inject 注解无法被 Dagger 构建。

2.2、 满足依赖

默认情况下, Dagger 像上面描述的通过构造一个所请求类型的实例来满足依赖。 当你请求一个 CoffeeMaker 的时候它将通过调用 new CoffeeMaker() 来获取一个实例,并将其注入到指定字段。

但是 @Inject 不能在任何情况下都正常工作:

  • 接口不能被构造。

  • 三方类不能被注解。

  • 配置对象必须被配置。

在这些 @Inject 存在不足的情况下,使用 @Provides注解方法满足依赖。 这个方法的返回值类型定义了它满足的依赖关系。

例如, provideHeader()Heater 被请求的时候会被调用:

@Provides static Heater provideHeater() {
  return new ElectricHeater();
}

对于 @Provides 函数来说它有可能依赖它自己。 这是一个当 Pump 杯请求的时候将返回一个 Thermosiphon 的情况:

@Provides static Pump providePump(Thermosiphon pump) {
  return pump;
}

所有的 @Provides 方法必须术语一个 module 。 下面的类被 @Module 注解:

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater() {
    return new ElectricHeater();
  }

  @Provides static Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

按照管理 @Provides 注解的函数用 provides 作为函数名前缀, @Module 注解的类用 Module 作为类名后缀。

2.3、 构建图表(Building Graph)

@Inject@Provides 注解的类通过他们的依赖关系被连接成 Graph 对象。 调用代码(类似于一般程序的 main 方法,或者 Android 的 Application )通过已经被明确定义的根集合访问 Graph 。 在 Dagger 2 中,这个集合被一个接口定义,接口中的的函数均无参且返回期望类型。 通过应用 @Component 注解(同时传送一个 module 类型给 moudles 参数),Dagger 2 将会生成一个完整的实现(按照接口要求)。

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
  CoffeeMaker maker();
}

这个实现具有和接口相同的名字(只是多了 Dagger 前缀)。 通过调用接口实现类的 builder() 函数获取一个实例,并且通过返回的 builder 设置依赖并且构建一个新的实例。

注意: 如果你的 @Component 并非顶级类, 生成的 component 的名字将包含它的包含类的名字(通过下划线链接)。 例如:

class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

将生成的 component 名字为:DaggerFoo_Bar_BazComponent

任何 module 的默认构造函数都能被省略,因为如果没有设置构建器将自动构造实例。 任何 module 的 @Provides 方法都应该添加 static 标记,它是实现无需实例化。 如果所有的依赖能在没有用户创建依赖实例的前提下被构造,生成的实例将有一个 create() 函数这个函数能在不操作 builder 的前提下创建新实例。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

现在我们的 CoffeeApp 能方便的使用 Dagger 生成的 CoffeeShop 获取完全注入的 CoffeeMaker

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

现在 graph 被构建同时实体点被注入,我们运行我们的 coffee maker app。

$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
 [_]P coffee! [_]P

Last updated