我对依赖注入的理解是它可以快速地允许某人切换实现或使用测试实现。我试图了解你是如何期望用匕首做的。对我来说,似乎你应该能够切换出模块实现,但这似乎不受dagger的支持。什么是惯用的方法。
例如:
@component{modules = UserStoreModule.class}
class ServerComponent {
Server server();
}
class UserStoreModule {
@Provides
UserStore providesUserStore() {
return // Different user stores depending on the application
}
}
假设用户存储是一个接口,如果我希望能够根据情况使用mysql UserStore或redis UserStore,该怎么办?我需要两个不同的服务器组件吗?直观地,我觉得我应该能够切换出我在DaggerServerComponent.builder()中使用的用户存储,因为这样的代码比多个组件少得多。
答案 0 :(得分:3)
从概念上讲,依赖注入确实允许某人切换实现或使用测试实现":您已经编写了类来接受UserStore的任何实现,并且可以提供任意一个在构造函数中调用测试。无论你是否使用Dagger都是如此,并且在设计方面是一个很大的优势。
然而,Dagger最突出的特性 - 它的编译时代码生成 - 使得它比Spring或Guice等替代品更受限制。因为Dagger在编译时生成它需要的类,所以它需要确切地知道它可能遇到的实现,以便它可以准备这些实现&#39;依赖。因此,您无法在运行时接受任意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(); }
使用不同的行为创建Module的子类。这使您无法使用@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();
当然,您的模块甚至可以委托给任意外部Provider<UserStore>
,此时它将等同于component dependency。但是,如果你想使用Dagger来生成你所依赖的提供者或组件,除了将你的图形分成更小的部分之外,这种技术不会对你有所帮助。
在编译时连接两种类型,并且仅在运行时使用一种类型。这需要Dagger为所有选项准备注入代码,但允许您通过提供Module参数进行切换,甚至允许您更改提供的对象(如果使用可变参数或从对象图中读取值) 。请注意,与@Binds
相比,您仍然会有更多的开销,Dagger仍然会为您的选项生成代码&#39;依赖关系,但这里的选择过程清晰,高效,并且对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");
}
}
如果您确实需要在运行时决定,请注入多个提供商并从中进行选择。如果您只需要在编译时或测试时选择,则应使用模块覆盖或单独的组件。