반응형
이번 포스트에서 다룰 내용은 Qualifier
어노테이션입니다. 이름 그대로 객체를 식별해주는 어노테이션입니다.
@Named Qualifer
Dagger는 의존성 주입을 요청했을 때 Module에 선언된 자료형(Type)과 동일한 객체를 주입해줍니다.
따라서, Module에 동일한 Type의 객체가 여러개 선언되어 있을 경우, 컴파일 타임에 에러를 발생하게 됩니다.
의존성주입
은 Interface를 기반으로하는 코드를 권장하므로, 같은 부모를 가진 여러 Child 클래스가 존재할 가능성이 매우 높습니다.
이러한 경우를 위해서 @Named
어노테이션을 통해서 같은 타입의 여러 객체를 선언하고 주입해줄 수 있습니다.
사용법은 간단합니다. Module의 @Provides어노테이션과 @Injection 어노테이션에 각각 @Named
어노테이션을 통해 어떠한 객체를 주입할 건지 선언해 주면 Module에 @Named
로 선언된 객체와 동일한 객체를 주입해줄 수 있습니다.
ComputerComponent.kt
@Component(modules = [ComputerModule::class])
interface ComputerComponent {
fun getComputerB(): ComputerB // Provision Method
fun inject(computerA: ComputerA) // Member - Injection
@Component.Builder
interface Builder {
fun computerModule(computerModule: ComputerModule): Builder
fun application(@BindsInstance context: Context): Builder
fun build(): ComputerComponent
}
}
ComputerModule.kt
@Module
class ComputerModule {
@Named("M1")
@Provides
fun provideM1Cpu(): Cpu {
println("create Cpu")
return M1Cpu()
}
@Named("I7")
@Provides
fun provideI7Cpu(): Cpu {
println("create Cpu")
return I7Cpu()
}
@Named("Ram4G")
@Provides
fun provideRam4G(): Ram {
println("create Ram")
return Ram4G()
}
@Named("Ram8G")
@Provides
fun provideRam8G(): Ram {
println("create Ram")
return Ram8G()
}
}
Cpu.kt
interface Cpu {
val name: String
val company: String
}
class I7Cpu : Cpu {
override val name: String
get() = Name
override val company: String
get() = "Intel"
companion object {
const val Name = "I7 CPU"
}
}
class M1Cpu : Cpu {
override val name: String
get() = "M1 Chip Cpu"
override val company: String
get() = "Apple"
companion object {
const val Name = "M1 Chip Cpu"
}
}
Ram .kt
interface Ram {
val name: String
val company: String
}
class Ram4G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Samsung"
companion object {
const val Name ="4G RAM"
}
}
class Ram8G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Hynix"
companion object {
const val Name ="8G RAM"
}
}
class ComputerA {
@Named("M1")
@Inject
lateinit var cpu: Cpu
@Named("Ram4G")
@Inject
lateinit var ram: Ram
}
class ComputerB @Inject constructor(@Named("I7") val cpu: Cpu, @Named("Ram8G") val ram: Ram)
Test Code
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
private lateinit var computerComponent: ComputerComponent
private lateinit var context: Context
@Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().targetContext
computerComponent = DaggerComputerComponent.builder().application(context).build() // Component Builder
}
@Test
fun testComputerMemberInjection() {
val computerA =
ComputerA()
computerComponent.inject(computerA) // Field Inject
Assert.assertEquals(M1Cpu.Name, computerA.cpu.name)
println("computerA's cpu = ${computerA.cpu.name}")
println("computerA's ram = ${computerA.ram.name}")
}
@Test
fun testComputerConstructorInject() {
val computerB = computerComponent.getComputerB() // Constructor Inject
Assert.assertEquals(I7Cpu.Name, computerB.cpu.name)
println("computerB's cpu = ${computerB.cpu.name}")
println("computerB's ram = ${computerB.ram.name}")
}
}
Test 결과
사용자 정의 Qualifer
사용자 정의 식별자는 @Named
어노테이션에 String을 넣어주었던 것과는 다르게 직접 어노테이션을 생성해서 주입해줄 수 있습니다.
사용법은 @Named와 동일하게 Module의 @Provides와 @Inject에 각각에 정의했던 어노테이션을 선언해주면 해당하는 객체를 주입받을 수 있습니다.
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetM1Cpu
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetI7Cpu
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetRam4G
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GetRam8G
@Module
class ComputerModule {
@GetM1Cpu
@Provides
fun provideM1Cpu(): Cpu {
println("create Cpu")
return M1Cpu()
}
@GetI7Cpu
@Provides
fun provideI7Cpu(): Cpu {
println("create Cpu")
return I7Cpu()
}
@GetRam4G
@Provides
fun provideRam4G(): Ram {
println("create Ram")
return Ram4G()
}
@GetRam8G
@Provides
fun provideRam8G(): Ram {
println("create Ram")
return Ram8G()
}
}
class ComputerA {
@GetM1Cpu
@Inject
lateinit var cpu: Cpu
@GetRam4G
@Inject
lateinit var ram: Ram
}
class ComputerB @Inject constructor(@GetI7Cpu val cpu: Cpu, @GetRam8G val ram: Ram)
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
private lateinit var computerComponent: ComputerComponent
private lateinit var context: Context
@Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().targetContext
computerComponent = DaggerComputerComponent.builder().application(context).build() // Component Builder
}
@Test
fun testComputerMemberInjection() {
val computerA =
ComputerA()
computerComponent.inject(computerA) // Field Inject
Assert.assertEquals(M1Cpu.Name, computerA.cpu.name)
println("computerA's cpu = ${computerA.cpu.name}")
println("computerA's ram = ${computerA.ram.name}")
}
@Test
fun testComputerConstructorInject() {
val computerB = computerComponent.getComputerB() // Constructor Inject
Assert.assertEquals(M1Cpu.Name, computerB.cpu.name)
println("computerB's cpu = ${computerB.cpu.name}")
println("computerB's ram = ${computerB.ram.name}")
}
}
반응형