出来混迟早要还的,技术债Dagger2(终章):@SubComponent解决“混乱”依赖关系

编程入门 行业动态 更新时间:2024-10-07 19:21:42

出来混迟早要还的,技术债Dagger2(终章):@SubComponent解决“<a href=https://www.elefans.com/category/jswz/34/1764548.html style=混乱”依赖关系"/>

出来混迟早要还的,技术债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都是唯一了,也就代表了ComponentScope是唯一的。

因此我们需要进一步拆,那就是拆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) :AppComponentSubcomponent。包含了只有LoginActivity才需要依赖的内容。

  • 3、UserComponent(@UserScope):依赖于AppComponent(但并不是SubComponent)。包含用户模块相关的依赖。该组件还包含另外两个Subcomponents(@ActvityScope) :HomeComponent和ItemsComponent。

  • 4、HomeComponent(@ActivityScope) :UserComponentSubcomponent。它还包含三个Fragment:ProfileFragment、StatsFragment和MovesFragment。

  • 5、ItemsComponent(@ActivityScope):UserComponentSubcomponent

基于上述文字,咱们来看一张图:

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解决“混乱”依赖关系

本文发布于:2024-02-13 09:09:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1757682.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:混乱   关系   技术   终章   SubComponent

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!