混乱”依赖关系"/>
出来混迟早要还的,技术债Dagger2(终章):@SubComponent解决“混乱”依赖关系
前言
实话实说,这是一个毁誉参半的框架。较陡的学习曲线和其所带来的收益并不成正比。而且一旦没办法carry全场的话,很容易一顿操作猛如虎,一看战绩0-5...
所以我决定在这篇文章结束后,暂时停止后续Dagger的文章。
一个框架的出现,一定是为了解决某一类问题。这就如同@Component
和@SubComponent
关系一样。当我们使用@Component
能够满足我们的日常开发时,@SubComponent
是不会出现的,除非谁“脑子有泡”,瞎造轮子。
所以@SubComponent
势必是为了解决@Component
所不能解决的问题...
正文
本文参考了:Dagger 2 : Component Relationships & Custom Scopes
需要自备梯子
前系列文章,我们由渐入深的逐步应用了Dagger2:
出来混迟早要还的,技术债Dagger2:基础篇
出来混迟早要还的,技术债Dagger2:Android篇(上)
出来混迟早要还的,技术债Dagger2:Android篇(中)@Scope、@Singleton
出来混迟早要还的,技术债Dagger2:Android篇(下)进一步理解Dagger
一、隐隐出现的问题
项目之初,万物起源,业务小需求少,太极生两仪,两仪生四象,四象生八卦...亦步亦趋,代码结构简单,写起来爽,维护起来也不费劲。
此时我们使用Dagger时,通常从单个AppComponent
和带有@Singleton
的单个AppModule
开始。
在这种情况下,Dagger生成一个DaggerAppComponent
,该组件与AppModule
具有HAS-A关系。完全ojbk,写起来也很爽,直来直去!
但是随着我们的App规模的增长,我们很快就会意识到AppModule
开始变成一个拥有各种依赖的上帝模块(god module)。越来越多的需求出现,越来越多的依赖需要加入到AppModule
,有用的没用的通通需要加入...
重构迫在眉睫...
这里我们会很顺其自然的想到一些方案:既然我们的AppModule
开始变成一个拥有各种依赖的上帝模块。那我们拆Module
不就ok啦。
一般来说拆Module,可以分为横向拆和纵向拆。
1.1、横向拆Module
我们大概会这样重构:
@Component(modules = {AppModule.class, ApiModule.class})
public interface AppComponent {// 省略
}
复制代码
1.2、纵向拆Module
@Component(modules = AppModule.class)
public interface AppComponent{// 省略
}@Module(includes = ApiModule.class)
public class AppModule{// 省略
}
复制代码
这样似乎,“完美的解决了我们的问题”?但是事实是这样么?问题总是各种各样:
如果我们需要对不同的Module在不同@Scope
,提供不一样生命周期的单例时。我们只能干瞪眼!因为无论再怎么拆Module,Component都是唯一了,也就代表了Component
的Scope
是唯一的。
因此我们需要进一步拆,那就是拆Component。
二、拆Component通用方案
方案一:低层Component依赖高层Component
此方案的思路和我们正常“拆Module”的思路很类似。也就是从高层Component中抽出抽象内容,然后下沉到低层Component之中。
这种方案的具体实现会在下文中具体展开。
方案二:@SubComponent
通过@SubComponent
让我们不同的Component拥有类似于父/子类的继承关系。
这种方案的具体实现会在下文中具体展开。
接下来咱们会通给一个真正的例子,来感受一下这俩种不同方案下的拆Component的。
2.1、“需求评审”
假设我们现在需要一个用户模块,负责用户登录/登出等操作。
红框的内容不用纠结,其实就是一个“用户的名字”。
简单解释一下这个图的流程,从左到右,表示应用启动。然后通过LoginComponent
“pikachu”(皮卡丘...看眼这篇文章的老外作者是个《神奇宝贝》的粉丝)这个用户登录,然后进去HomeComponent
,也就是正常的操作页面,当然后边的ItemComponent
也是(只不过是另一个模块的界面而已)。然后再次进入LoginComponent
登出了账号...接下来“jiigglypuff”登录账号,重复上述过程...
2.2、代码依赖图
-
1、
AppComponent
(@Singleton
) :Singleton作用域组件,应用程序的主要组件。通常在Application上被创建并持久化。 -
2、
LoginComponent
(@ActivityScope
) :AppComponent
的Subcomponent
。包含了只有LoginActivity才需要依赖的内容。 -
3、
UserComponent
(@UserScope
):依赖于AppComponent(但并不是SubComponent)。包含用户模块相关的依赖。该组件还包含另外两个Subcomponents(@ActvityScope) :HomeComponent和ItemsComponent。 -
4、
HomeComponent
(@ActivityScope
) :UserComponent
的Subcomponent
。它还包含三个Fragment:ProfileFragment、StatsFragment和MovesFragment。 -
5、
ItemsComponent
(@ActivityScope
):UserComponent
的Subcomponent
。
基于上述文字,咱们来看一张图:
2.3、代码实现方案一:低层Component依赖高层Component
2.3.1、AppComponent
@Singleton
@Component(modules = arrayOf(AppModule::class))
interface AppComponent {// 用于@SubComponent,此时先按下不表 fun loginBuilder(): LoginComponent.Builder// 不需要关心BaseSchedulerProvider是干啥的。fun schedulerProvider(): BaseSchedulerProvider
}
复制代码
AppComponent
作为我们的顶级Component,势必会被其他的Component所依赖。但是其他的Component无法直接持有AppComponent
的实例,依次也就没办法通过AppComponent
的实例,调用到它其中所提供的依赖。
这里可不是继承,所以没有办法像子类那样调用到父类的变量/方法。
这很好理解吧?就如同我们Android项目,module之间的implementation
。低层模块想要用高层模块,我们常用的方式就是在底层模块写一个接口(Service),然后高层模块去实现这个接口。然后将实现接口的具体类,以多态的形式register到底层模块中。这样就完成了底层模块调用高层模块。
对于我们的Dagger也是如此。如果下层的Component想要使用我们的AppComponent
同样也是需要在AppComponent
中提供这样的接口方法。也就是fun schedulerProvider(): BaseSchedulerProvider
。
一会我们会在下级Component(
UserComponent
)中,来感受这个方法的作用。
2.3.2、MyApplication
顶级Component,AppComponent
的实例化。
class MyApplication : Application() {lateinit var appComponent: AppComponentprivate setoverride fun onCreate() {super.onCreate()appComponent = DaggerAppComponent.builder().application(this).build()}companion object {lateinit var app: MyApplicationinternal set}
}
复制代码
2.3.3、UserComponent
@Component(dependencies = arrayOf(AppComponent::class), modules = arrayOf(UserModule::class))
@UserScope
interface UserComponent {@Component.Builderinterface Builder {fun build(): UserComponent;@BindsInstancefun pokeMon(pokemon: Pokemon): Builder}
}
复制代码
Pokemon是一个关于用户信息的实体类,此Class包含了我们用户登录的返回信息。(不需要多么在意,脑海中YY下就OK了。)
简单解释一下:
-
1、
dependencies = arrayOf(AppComponent::class)
:告诉Dagger2UserComponent
,依赖于AppComponent
。 -
2、我们在
UserComponent
中声明@Component.Builder
。对于UserComponent
,它的实例化,只需要一个Pokemon
实例,所以只需要一个@BindsInstance
即可。
关于
Component.Builder
/@BindsInstance
的用法咱们之前的文章提到过。
接下来近一步来看一下代码:
2.3.4、UserManager
UserComponent
的实例化。
@Singleton
class UserManager @Inject constructor(private val service: PokemonService) {var userComponent: UserComponent? = nullprivate setpublic fun createUserSession(pokemon: Pokemon) {userComponent = DaggerUserComponent.builder().appComponent(MyApplication.app.appComponent).pokeMon(pokemon).build()}
}
复制代码
填一下上文中提到的坑:fun schedulerProvider(): BaseSchedulerProvider
:
Dagger中如何通过AppComponent
中的fun schedulerProvider(): BaseSchedulerProvider
方法,为我们的下层Component生成提供依赖的代码呢?
直接上编译生成类DaggerUserComponent
:
public final class DaggerUserComponent implements UserComponent {private Provider<BaseSchedulerProvider> schedulerProvider;private void initialize(final Builder builder) {this.schedulerProvider =new AppComponent_schedulerProvider(builder.appComponent);// 省略部分代码}private static class AppComponent_schedulerProvider implements Provider<BaseSchedulerProvider> {private final AppComponent appComponent;@Overridepublic BaseSchedulerProvider get() {return appComponent.schedulerProvider();}// 省略部分代码}
}
复制代码
我们可以看到,AppComponent
中的fun schedulerProvider(): BaseSchedulerProvider
,在DaggerUserComponent
中以private Provider<BaseSchedulerProvider> schedulerProvider;
的形式存在。
Provider<BaseSchedulerProvider>
是一个接口,会以内部类的形式被实现,而其中的get方法,是通过appComponent.schedulerProvider()
对外提供的。
而appComponent
是在initialize(final Builder builder)
中伴随着Builder
一同传入。也就是我们实例化DaggerUserComponent时:appComponent(MyApplication.app.appComponent)
代码实现方案一到此就结束了,不知道各位小伙伴有没有get到~接下来让我们来看一看代码实现方案二。
2.4、代码实现方案二:@SubComponent
2.4.1、LoginComponent
@ActivityScope
@Subcomponent(modules = arrayOf(LoginModule::class))
interface LoginComponent {@Subcomponent.Builderinterface Builder {@BindsInstancefun loginActivity(loginActivity: LoginActivity): Builder}
}
复制代码
仅这样还不够,我们还需要改造一下AppComponent
,不然我们的Dagger并不知道该如何去实例化LoginComponent
。改造很简单,还记不记得我们上文提到按下不表的内容?
OK,就这样我们的LoginComponent
已经完成了编写。
2.4.2、LoginActivity
实例化LoginComponent
class LoginActivity {private lateinit var loginComponent: LoginComponentfun initDagger(appComponent: AppComponent) {loginComponent = appComponent.loginBuilder() // 我们在AppComponent留下的方法.loginActivity(this).build()}// 省略无关紧要的内容
}
复制代码
此时让我们再来看一下DaggerAppComponent
:
public final class DaggerAppComponent implements AppComponent {this.loginBuilderProvider =new Factory<LoginComponent.Builder>() {@Overridepublic LoginComponent.Builder get() {return new LoginComponentBuilder();}};}private final class LoginComponentBuilder implements LoginComponent.Builder {// 省略部分代码}// 注意理解这里private final class LoginComponentImpl implements LoginComponent {private LoginComponentImpl(LoginComponentBuilder builder) {assert builder != null;// 初始化LoginComponent具体所提供的依赖initialize(builder);}// 省略部分代码}
}
复制代码
Dagger生成一个LoginComponentImpl,它是DaggerAppComponent的一个内部类。由于内部类可以访问其外部类的成员变量,因此,可以直接从AppComponent访问LoginComponent所需的任何AppComponent依赖项,而无需显式公开这些依赖项。
也就无需像方案一那样:fun schedulerProvider(): BaseSchedulerProvider
对于LoginComponent
来说,只要它可以从AppComponent
中拿到自己所需的依赖,我们就无需额外的声明。
因此我们再来回过头看看开始贴出来的图:
对于LoginComponent
来说,它是AppComponent
的内部类,所以可以直接从AppComponent
中获取自己所需要的内容。
而UserComponent
则不同,它需要AppComponent
通过明确的方法对外提供依赖,这样UserComponent
才可以拿到自己所需的依赖。
三、对比总结
DaggerUserComponent:
- DaggerUserComponent依赖AppComponent。
- DaggerUserComponent不知道AppComponent的任何实现,只知道通过AppComponent接口公开的依赖项。
- DaggerUserComponent只能通过schedulerProvider调用AppComponent对外提供的依赖方法。
LoginComponentImpl:
- LoginComponentImpl是AppComponent的子Component。
- LoginComponentImpl是DaggerAppComponent的内部类。
- LoginComponentImpl可以直接从AppComponent中拿到自己所需的依赖, 而不需要任何额外的内容。
所以,对于我们来说。当我们不同的Component之间耦合的比较严重时,可以使用@SubComponent;反之我们可以使用方案一:Component依赖。
尾声
到这就接近于尾声了,不知道有多少小伙伴能一路看到这。我在写这个系列的时候一直是“懵懵懂懂”。当写到这一篇的时候,逐渐有了一些豁然开朗的感觉。
扯点题外话,Dagger对于我们Android来说到底好用么?当我们的项目足够大,各种实现足够繁琐时。如果我们拥有较为合理的依赖注入,我们可以非常方便的使用某个对象。举个我们项目中的小例子:
我需要一个接口的实现类,这个接口大概这个样子:
interface IItemAction {fun showActionsDialog(activity: Activity,// 省略巨多的参数)
}
复制代码
我如果需要手动new这个IItemAction
,我们需要传递它所需要的所有参数,而且有的参数的实例化还需要更多其他的参数...
而如果我们项目拥有较为完善的依赖注入时,我只需要这么做:
@Inject
lateinit var action: IItemAction
复制代码
就酱,这篇文章结束了。
我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~
转载于:
更多推荐
出来混迟早要还的,技术债Dagger2(终章):@SubComponent解决“混乱”依赖关系
发布评论