我对依赖注入的理解是它允许某人快速切换实现或使用测试实现。我试图了解你应该如何用匕首做到这一点。对我来说,您似乎应该能够切换模块实现,但这似乎不受 dagger 支持。什么是惯用的方式来做到这一点。
例如:
@component{modules = UserStoreModule.class}
class ServerComponent {
Server server();
}
class UserStoreModule {
@Provides
UserStore providesUserStore() {
return // Different user stores depending on the application
}
}
Run Code Online (Sandbox Code Playgroud)
假设用户存储是一个接口,如果我希望能够根据情况使用 mysql UserStore 或 redis UserStore 怎么办。我需要两个不同的服务器组件吗?直觉上,我觉得我应该能够切换出我在 DaggerServerComponent.builder() 中使用的用户存储,因为这比多个组件要少得多的代码。
从概念上讲,确实依赖注入“允许某人切换实现或使用测试实现”:您已经编写了类来接受 UserStore 的任何实现,并且可以在构造函数调用中提供任意一个以进行测试。无论您是否使用 Dagger,情况都是如此,这是设计上的一大优势。
然而,Dagger 最突出的特性——它的编译时代码生成——使它在这里比 Spring 或 Guice 等替代品更受限制。因为 Dagger 在编译时生成它需要的类,所以它需要确切地知道它可能遇到哪些实现,以便它可以准备这些实现的依赖项。因此,您不能Class<? extends UserStore>在运行时接受任意内容并期望 Dagger 填补其余部分。
这让您有几个选择:
创建两个单独的 Module 类,每个实现一个,并使用它们让 Dagger 生成两个单独的组件。这将生成最高效的代码,尤其是在使用 时@Binds,因为 Dagger 不需要为您未绑定的实现生成任何代码。当然,虽然这允许您重用您的类和一些模块,但它不允许在运行时做出实现之间的决定(缺少在整个 Dagger 组件实现之间进行选择)。
此选项只需要很少量的手写代码,但会在组件实现中生成大量额外代码。它可能不是您要查找的内容,但包含它以突出其与其他内容的差异,并且仍应在可能的情况下使用。
@Module public interface MySqlModule {
@Binds UserStore bindUserStore(MySqlUserStore mySqlUserStore);
}
@Module public interface RedisModule {
@Binds UserStore bindUserStore(RedisUserStore redisUserStore);
}
@Component(modules = {MySqlModule.class, OtherModule.class})
public interface MySqlServerComponent { Server server(); }
@Component(modules = {RedisModule.class, OtherModule.class})
public interface RedisServerComponent { Server server(); }
Run Code Online (Sandbox Code Playgroud)
创建具有不同行为的模块的子类。这会阻止您使用@Binds或 静态/最终@Provides方法,导致您的@Provides方法采用(并为其生成代码)不必要的额外依赖项,并要求您在依赖项可能更改时显式地进行和更新构造函数调用。由于其脆弱性和优化机会成本,我不会在大多数情况下推荐此选项,但对于有限的情况,例如在测试中替换依赖轻假货,它可能很方便。
@Module public class UserStoreModule {
@Provides public abstract UserStore bindUserStore(Dep1 dep1, Dep2 dep2, Dep3 dep3);
}
public class MySqlUserStoreModule extends UserStoreModule {
@Override public UserStore bindUserStore(Dep1 dep1, Dep2 dep2, Dep3 dep3) {
return new MySqlUserStore(dep1, dep2);
}
}
public class RedisUserStoreModule extends UserStoreModule {
@Override public UserStore bindUserStore(Dep1 dep1, Dep2 dep2, Dep3 dep3) {
return new RedisUserStore(dep1, dep3);
}
}
DaggerServerComponent.builder()
.userStoreModule(
useRedis
? new RedisUserStoreModule()
: new MySqlUserStoreModule())
.build();
Run Code Online (Sandbox Code Playgroud)
当然,你的 Module 甚至可以委托给一个任意的 external Provider<UserStore>,此时它就等同于一个组件依赖。但是,如果您想使用 Dagger 来生成您所依赖的 Provider 或 Component,那么除了将图形分解成更小的部分之外,这种技术对您无济于事。
在编译时连接这两种类型,并在运行时只使用一种。这需要 Dagger 为所有选项准备注入代码,但允许您通过提供 Module 参数进行切换,甚至允许您更改提供的对象(如果您使用可变参数或从对象图中读取值) . 请注意,与 with 相比,您仍然会有更多的开销@Binds,并且 Dagger 仍会为您的选项的依赖项生成代码,但这里的选择过程清晰、高效且对 Proguard 友好。
这可能是最好的通用解决方案,但对于测试实现来说并不理想;让特定于测试的代码潜入生产环境通常是不受欢迎的。对于这种情况,您将需要模块覆盖或单独的组件。
@Module public class UserStoreModule {
private final StoreType storeType;
UserStoreModule(StoreType storeType) { this.storeType = storeType; }
@Provides UserStore provideUserStore(
Provider<MySqlUserStore> mySqlProvider,
Provider<RedisUserStore> redisProvider,
Provider<FakeUserStore> fakeProvider) {
switch(storeType) {
case MYSQL: return mySqlProvider.get();
case REDIS: return redisProvider.get();
case FAKE: return fakeProvider.get(); // you probably don't want this in prod
}
throw new AssertionError("Unknown store type requested");
}
}
Run Code Online (Sandbox Code Playgroud)
当您确实需要在运行时做出决定时,请注入多个 Provider 并从中进行选择。如果只需要在编译时或测试时进行选择,则应使用模块覆盖或单独的组件。
| 归档时间: |
|
| 查看次数: |
1010 次 |
| 最近记录: |