DI
Dependency Injection: 依赖注入
DI是一种软件设计模式,实现了控制反转(Inversion of Control),主要作用是让一个对象接收它依赖的对象.
依赖:接收方需要的对象
注入:将依赖传递给接收方的过程,注入后接收方才能调用依赖;一般来说注入可以通过构造函数、setter、或接口实现
主要优势在于解耦接收方和依赖的具体实现,更容易实现扩展和重构
传统方式
class Computer {
fun play(gameName: String) {
println("play $gameName")
}
}
class Person {
private final val computer = Computer()
public fun playGame(gameName: String) {
computer.play(gameName)
}
}
fun main(){
val person = Person()
person.playGame("game")
}
这里的问题在于代码的可维护性和耦合性,当依赖项需要修改时,需要手动修改所有使用该依赖项的代码.
具体来说,如果Person想要使用Computer时,这个Computer可以时自己的,也可以时其他人、公司的电脑,这个电脑是外部提供的.
因此我们可以把这个Computer实例使用依赖注入的方式实现.
四种方式
-
构造函数注入
class Person1(private val computer: Computer){ public fun playGame(gameName: String) { computer.play(gameName) } } -
方法注入
class Person2() { public fun playGame(computer: Computer, gameName: String) { computer.play(gameName) } } -
属性注入
class Person3() { lateinit var computer: Computer fun setComputer(computer: Computer){ this.computer = computer } public fun playGame(gameName: String) { computer.play(gameName) } } -
接口注入
interface IComputer { fun play(gameName: String) } class WindowsComputer : IComputer { override fun play(gameName: String) { println("windows computer play $gameName") } } class MacComputer : IComputer { override fun play(gameName: String) { println("mac computer play $gameName") } } interface Gamer { fun setComputer(computer: IComputer) fun playGame(gameName: String) } class Person4 : Gamer { lateinit var computer: IComputer override fun setComputer(computer: IComputer) { this.computer = computer } override fun playGame(gameName: String) { computer.play(gameName) } }
Dagger2
Dagger2相比于Dagger使用了编译时生成代码,比Dagger运行时反射性能更好
基本概念
依赖需求方:需要依赖对象的类.在上面的例子中,Person类就是依赖需求方
依赖对象:被依赖对象的类.Computer是依赖对象
依赖供应方:提供依赖对象,类似于工厂类,即创建依赖方
依赖注入器:负责将依赖对象注入到需求方;在实际代码中是一个接口,Dagger2在编译时自动生成这个接口的实现类.
实例
依赖供应方
@Module// 依赖供应商.提供Computer实例
class TaoBao {
//Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
@Provides
fun getComputer(): Computer {
return Computer("tao bao")
}
}
这里的Taobao类表示这个Computer实例来自于taobao,@Module注解告诉Dagger这个类是依赖供应方,@Provides使用在被@Module标记类的方法上,表示实例化依赖方的具体过程.
//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class])
interface ZTOExpress {
fun deliverTo(person: Person)
}
ZTOExpress就是一个接口形式的依赖注入器,这里@Component注解告诉了Dagger从Taobao这个依赖供应方里拿依赖方,并传递给Person;
如果有多个依赖供应方,我们也可以写成这样的形式
//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class,JD::class])
interface ZTOExpress {
fun deliverTo(person: Person)
}
这里有个问题.依赖注入器中声明了两个供应商,Dagger不知道从哪个供应商中取实例,这里就可以用@Qulifier或者@Named注解区分
上面的过程生成了一个Computer实例,但此时依赖注入器依然不知道需要把Computer注入给哪一个Person实例,我们可以在Person类中使用@inject标记
data class Person(val name: String) {
@Inject
lateinit var computer: Computer
fun playGame(gameName: String) {
computer.play(gameName)
}
}
@Inject用于构造方法
data class Computer(val name:String) {
@Inject
constructor() : this(name = "default") {
Log.i("Basti611","Computer constructor")
}
fun play(gameName: String) {
Log.i("Basti611","computer name: $name")
Log.i("Basti611","playing $gameName")
}
}
当inject标记构造函数时,如果依赖注入器找不到依赖供应商提供的依赖方生成方法,就去通过被@Inject标记的构造方法生成依赖对象.
@Inject标记构造方法生成一个实例比@Module写依赖工厂类方便很多,但如果Computer是三方类,无法修改那么只能通过@Module提供对象
依赖对象还有依赖
添加一个CPU类,并添加到Computer的依赖,同时在Taobao工厂类中提供CPU的实例化方法
data class CPU(
val name: String
)
data class Computer @Inject constructor(val name: String, val cpu: CPU) {
fun play(gameName: String) {
Log.i("Basti611", "computer name: $name")
Log.i("Basti611", "playing $gameName")
}
}
@Module// 依赖供应商.提供Computer实例
class TaoBao {
//Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
// @Provides
fun getComputer(): Computer {
return Computer("tao bao")
}
@Provides
fun getCPU(): CPU {
return CPU("CPU")
}
}
流程:getComputer()的@Provides方法添加了一个CPU类型的参数,Dagger在获取Computer实例时,发现这个方法依赖于CPU,就会去找CPU的提供方法,拿到CPU后再交给Computer的实例化方法,最终获取到一个Computer并完成依赖注入
- 查找 @Module 类中是否存在创建该类的方法
- 如果存在,查看该方法是否存在参数 a. 存在参数,则按从步骤1 开始依次初始化每个参数 b. 不存在参数,则直接初始化该类实例,注入到依赖需求方
- 如果不存在,则查找该类中被 @Inject 标识的构造方法 a. 如果构造函数有参数,则按照从步骤1 开始依次初始化每个参数 b. 如果构造函数没有参数,则直接初始化该类实例,一次依赖注入到此结束
以上总结就是递归构造,优先找@Provides,如果没有再找@Inject
@Named @Qulifier
如果依赖方供应商Taobao中有两个提供Computer的方法,编译时会报错
@Module// 依赖供应商.提供Computer实例
class TaoBao {
//Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
// @Provides
fun getComputer(): Computer {
return Computer("tao bao")
}
@Provides
fun getAmdCPU(): CPU {
return CPU("AMD")
}
@Provides
fun getIntelCPU(): CPU {
return CPU("INTEL")
}
}
Dagger会显示CPU被绑定了多次,因此Dagger无法知道通过调用哪个方法.
这里可以通过@Named注解,在返回值类型相同的情况下通过@Named注解的value值区分.
@Module// 依赖供应商.提供Computer实例
class TaoBao {
//Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
// @Provides
fun getComputer(cpu: CPU): Computer {
return Computer("tao bao", cpu)
}
@Provides
@Named("AMD")
fun getAmdCPU(): CPU {
return CPU("AMD")
}
@Provides
@Named("INTEL")
fun getIntelCPU(): CPU {
return CPU("INTEL")
}
}
data class Computer @Inject constructor(val name: String, @Named("AMD") val cpu: CPU) {
fun play(gameName: String) {
Log.i("Basti611", "computer name: $name")
Log.i("Basti611", "playing $gameName")
}
}
依赖注入器依赖其他依赖注入器
如果一个供应方无法完成所有实例的供应,需要其他供应方提供实例,可以使用依赖注入器的依赖注入器,首先提供一个新的CPU供应方:
@Module
class CPUProvider {
@Provides
@Named("AMD")
fun getAmdCPU(): CPU {
return CPU("AMD")
}
@Provides
@Named("INTEL")
fun getIntelCPU(): CPU {
return CPU("INTEL")
}
}
现在需要一个依赖注入器
@Component(modules = [CPUProvider::class])
interface Ups {
@Named("AMD") fun getAMDCPU():CPU
@Named("INTEL") fun getIntelCPU():CPU
}
这里要注意的是因为ups是一个子组件依赖注入器,所以不需要定义类似于injectTo的方法,只需要定义一个返回值是依赖方的方法.
最后修改依赖注入的方法
val person = Person("张三")
val upExpress = DaggerUps.builder().build()
DaggerZTOExpress.builder().jD(JD()).ups(upExpress).build().deliverTo(person)
person.playGame("games")
@SubComponent
如果一个依赖注入器过于复杂,可以拆分成如果若干个子依赖注入器,可以使用@SubComponent
@Subcomponent
interface ZTOShanghaiExpress {
fun deliverTo(person: Person)
}
ZTOExpress中应该有一个返回ZTOShanghaiExpress实例的方法
//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class,JD::class], dependencies = [Ups::class])
interface ZTOExpress {
fun deliverTo(person: Person)
fun getShanghaiZTOExpress(): ZTOShanghaiExpress
}
private fun init() {
val person = Person("张三")
val upExpress = DaggerUps.builder().build()
val ztoExpress = DaggerZTOExpress.builder().jD(JD()).ups(upExpress).build()
val shanghaiZTOExpress = ztoExpress.getShanghaiZTOExpress()
shanghaiZTOExpress.deliverTo(person)
person.playGame("games")
}
这里的ZTOShanghaiExpress虽然没有被@Module标记,但是因为他属于ZTOExpress,因此继承了父组件的注解.
Provider Lazy
Lazy和Porovider都用来包装需要被Dagger注入的类型,Lazy指延迟加载,也即是当需要用到时才会获取一个实例;Provide会强制重新加载,每次用到依赖对象时,都会注入一次
总结
什么情况下需要使用Dagger2
全局单例管理
在大型应用中,我们经常需要管理一些全局单例对象,例如网络请求库、数据库实例、或者共享的缓存对象。Dagger2 可以通过 @Singleton 注解轻松管理这些单例。
@Module
public class NetworkModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
@Provides
@Singleton
Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
@Component(modules = {NetworkModule.class})
@Singleton
public interface AppComponent {
Retrofit getRetrofit();
}
//使用
AppComponent appComponent = DaggerAppComponent.create();
Retrofit retrofit = appComponent.getRetrofit();
优点: 避免手动管理单例逻辑
依赖复杂的构造对象
当一个对象需要许多依赖时,手动构造非常繁琐,并且容易出错。Dagger2 能自动注入这些依赖,简化开发。
class AnalyticsService {
private final Context context;
private final String apiKey;
@Inject
AnalyticsService(Context context, String apiKey) {
this.context = context;
this.apiKey = apiKey;
}
}
@Module
public class AnalyticsModule {
@Provides
String provideApiKey() {
return "API_KEY";
}
@Provides
AnalyticsService provideAnalyticsService(Context context, String apiKey) {
return new AnalyticsService(context, apiKey);
}
}
@Component(modules = {AnalyticsModule.class})
public interface AppComponent {
AnalyticsService getAnalyticsService();
}
Activity/Fragment依赖注入
Dagger2 可以方便地管理 Activity 或 Fragment 中的依赖,例如 ViewModel、Presenter、或者其他上下文相关的实例。
@Module
public class ActivityModule {
private final Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
@Provides
Activity provideActivity() {
return activity;
}
}
@Component(modules = {ActivityModule.class})
public interface ActivityComponent {
void inject(MainActivity activity);
}
public class MainActivity extends AppCompatActivity {
@Inject
AnalyticsService analyticsService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityComponent component = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.build();
component.inject(this);
analyticsService.track("MainActivity started");
}
}