Clean Software/Dependency Injection

의존성 주입 방법 (생성자 주입, 서비스 로케이터, Dagger)

728x90
반응형

 

 

 

의존성

B 클래스에서 A 클래스 내부에 변수로 사용하게 됨으로써 B 클래스는 A 클래스에 의존관계가 생기게 되는 것

 

주입

내부가 아니라 외부에서 객체를 생성해서 넣어주는 것

 

의존성+주입

내부에서 만든 변수를 외부에서 넣어주어 의존성을 분리시켜주는 것

 

의존성 종류는 클래스 의존성뿐 아니라 메서드 의존성 등 다양하지만 여기선 다양한 객체를 그냥 클래스라고 명칭함

 

DI 장점

- 다형성을 사용할 수 있어 프로그램이 유연해진다. 

더보기

다형성 정의: 다형성이란 프로그램 언어 각 요소들(상수, 변수, 식, 객체, 메서드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질

다형성 구현방법: 오버 로딩(여러 종류의 타입을 받아들여 결국엔 같은 기능을 하도록 만들기 위한 작업), 오버 라이딩(오버 라이딩은 상위 클래스의 메서드를 하위 클래스에서 재정의하는 것), 함수형 인터페이스(람다식을 사용하기 위한 API로 자바에서 제공하는 인터페이스에 구현할 메서드가 하나뿐인 인터페이스)

mocking 이 쉬워져 테스트가 용이해진다. 

 

- 재사용성을 높여준다.

- 종속적이던 코드의 수가 줄어든다.

- 코드를 읽기 쉬워진다.

- 결합도는 낮추면서 유연성과 확정성을 향상시킬 수 있다. 

 

 

 

 


 

강하게 커플링되어있는 Original Code

class DataSource{
	fun getTasks(){   }
}
class Repository{
	private val localDataSource: DataSource = DataSource()
    
     fun getTasks() {
        localDataSource.getTasks()
    }
}
fun viewModel(){
	val repository: Repository = Repository()
	repository.getTask()
}

 

Repository 클래스 안에서 DataSource 객체가 생성된다.

즉 Repository 클래스를 만들려면 무조건 DataSource 객체가 내부에서 생성되어한다

DataSource클래스 생성 없이는 Repository 클래스가 존재할 수 없음  -> 강한 커플링

 


생성자 주입(Constructor injection) 예제 코드

class DataSource{
	fun getTasks(){   }
}
class Repository(private val localDataSource: DataSource){
    
     fun getTasks() {
        localDataSource.getTasks()
    }
}
fun viewModel(){
	val localDataSource: DataSource = DataSource()
	val repository: Repository = Repository(localDataSource)
	repository.getTask()
}

DataSource 객체는 Repository 밖에서 생성하기 때문에

Repository 객체를 만들 때 DataSource 와는 상관없이 생성할 수 있다.

다시 말해, Repository는 생성자에 파라미터로 DataSource를 주입해주었기 때문에

Repository와 DataSource 클래스 간에 커플링이 사라지게 되었다. 

(Repository를 만들 때 FakeDataSource를 만들어 사용할 수 있게 된다.)

 


필드 주입(Fidld Injection) 예제 코드

class DataSource{
	fun getTasks(){  }
}
class Repository(){
    lateint val localDataSource: DataSource
     fun getTasks() {
        localDataSource.getTasks()
    }
}
class MainViewModel{
	val repository: Repository = Repository()
	repository.localDataSource: DataSource = DataSource()
	
	repository.getTask()
}

 


서비스 로케이터(ServiceLocator) 예제 코드

class DataSource{
	fun getTasks(){   }
}
object ServiceLocator{
    fun getDataSource: DataSource = DataSource()
}
class Repository(){
    private val localDataSource: DataSource = ServiceLocator.getDataSource()
    
     fun getTasks() {
        localDataSource.getTasks()
    }
}
class MainViewModel{
	val repository: Repository = Repository()	
	repository.getTask()
}

DataSource와 Repository 간의 의존성을 알고 있는

ServiceLocator를 이용하여 의존성을 주입하였다. 

하지만 이 서비스 로케이터 패턴은 클래스를 구현할 때 코딩되어야 하기 때문에

밖에서 볼 때는 무슨 클래스가 필요한지 알기 어렵다. 

 


데거(Dagger) 예제 코드

< Dagger 어노테이션  >

@Module
- 타 클래스에 의존성이 있는 클래스들을 제공해주는(provide~) 클래스를 정의해주는데 쓰임
-  inject 어노테이션이 붙은 메서드(method)/ 생성자(constructor), 프로퍼티(Field)에 외부에서 객체를 생성하고 전달할 Module
- dagger는 inject 어노테이션이 붙은곳의 Type과  Module 메서드의 반환 Type을 비교하고 자동으로 의존성 주입을 해준다

@Component
- 실질적으로 코드를 짤 때 필요한 최상위 의존성 객체들을 전달받는(get~) 클래스를 만들 때 쓰임

- 연결된 Module을 이용하여 의존성 객체를 생성하고, Inject로 요청받은 인스턴스에 생성한 객체를 주입함
- 의존성을 요청받고 주입하는 Dagger의 주된 역할을 수행
- Component는 @Inject 어노테이션이 의존성 주입할 멤버변수와 생성자에 달려있는 것을 보고 DI 대상을 확인할 수 있음

@Inject

- inject 어노테이션이 붙은 생성자(constructor)/ 필드(Field)/메서드(method)의존성 주입을 받아 필요한 정보들을 주입받아 객체가 생성이 될 수 있음을 표시할때 쓰임
- Inject 어노테이션으로 의존성 주입을 요청하면 연결된 Component가 Module로 부터 객체를 생성하여 넘겨줌


 

class DataSource{
	fun getTasks(){   }
}

 

1) @Module 붙여 Class 만들고, @Provider 붙인 함수 구현하여 -> 의존성 주입할 객체를 제공하기

@Module
class AppModule {
	
	@Provides
	@Singleton
	fun provideDataSource(): DataSource{
		return Datasource()
	}
}

@Module 어노태이션을 붙인 AppModule 클래스는 Repository을 만들기 위해 필요한 모든 객체를 얻을 수 있다. 

그러나 위의 코드로만으로는 자동으로 필요한 객체를 생성해서 주입해주는 코드를 생성하라고 명령하지 못한다.

 

자동으로 주입해주는 코드를 생성해주기 위해서는 다른 하나의 어노태이션을 사용해야 한다.

@Provides 어노테이션을 붙여줌으로써 자동으로 객체를 생성해서 주입해주는 기능을 더할 수 있다. 

메서드 이름을 어떻게 하던 상관 없지만, provideXXX 와 같은 prefix를 붙이는 것이 관습적이다. 

(앱 내 어느 코드에서 주입을 받을 때마다 새로운 객체를 생성할 필요가 없기 때문에 @Singleton으로 선언)

 

 

2) @Componet 붙여 클래스 만들어 -> 실질적으로 코드를 짤 때 필요한 최상위 의존성 객체들을 전달 해줌

@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {

	    fun getRepository(repository: Repository)

}

 

이 어노태이션은 우리가 실질적으로 코드를 짤 때 필요한 최상위 의존성 객체들을 전달 받는 클래스를 만들 때 쓰인다.

@Component 어노태이션은 컴포넌트 클래스를 만들 때 포함시킬 모듈들을 modules 속성을 통해 지정할 수 있다.

위 코드에서는 AppModule를 넣어주었다. 

그리고 우린 inject() 라는 메서드를 정의하므로써 Repository 객체를 주입받을 수 있다.

 

 

@Component 어노태이션은 추상 클래스나 인터페이스에 붙이는데, 이 어노태이션을 붙여서 만든 인터페이스는 앞에 Dagger라는 prefix가 붙어서 클래스를 어노태이션 프로세서가 자동으로 생성해준다.

우리가 AppComponent 라는 컴포넌트 인터페이스를 만들면, 컴파일 타임 때 DaggerAppComponent라는 클래스가 자동으로 생성된다

 

 

3) @Inject 붙여 의존성 주입 요청하기

먼저 Repository 객체 주 생성자 앞에 @Inject 어노테이션을 붙여주어

Repository 객체 생성시 필요한 Datasource 객체를 주입받기를 요청하는 코드를 작성하였다. 

class Repository @Inject constructor(val localDataSource: DataSource){
     
     fun getTasks() {
        localDataSource.getTasks()
    }
}

 

 

이제 MainActivity에서 Repository 객체 주입받는 코드를 작성하면 다음과 같다. 

직접 getRepository 함수를 호출해서 Repository 객체를 얻어오고 있다. 

Dagger2는 생성된 Component 클래스의 빌더를 이용해서 각 모듈에 필요한 정보들을 전달할 수 있게 해준다. 

class MainActivity: AppCompactActivity() {

	private lateinit var repository: Repository
     
	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        DaggerAppComponent.builder()
			.carModule(CarModule())
			//.contextModule(ContextModule(this)) // 원래는 위에서 context 모듈도 구현했어야함
			.build()
			.getRepository()
		
        repository.getTask()
       
	}


}

 

 

 

 

 


힐트(Hilt) 예제 코드

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참고

https://developer.android.com/training/dependency-injection/manual

https://blog.naver.com/mym0404/221518079694

https://developer.android.com/codelabs/android-hilt#0

 

시간 될 때 꼭 전부 읽어보기!! 왕좌의 게임에 녹여서 데거를 7편에 걸쳐 설명한 블로그(영문) : https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb

 

 

* Hilt 테스트 가이드:

https://developer.android.com/training/dependency-injection/hilt-testing

 

 

Dagger와 Hilt 자세하게 설명 + 테스팅까지(영문):

https://www.raywenderlich.com/books/dagger-by-tutorials/v1.0/chapters/18-hilt-architecture-components#toc-chapter-022-anchor-001

 

Dagger 

https://www.charlezz.com/?p=1259

728x90
반응형