정상에서 IT를 외치다

[Dagger] Dagger step1 - Coffee 예제를 활용한 대거의 3가지 초기화 본문

안드로이드

[Dagger] Dagger step1 - Coffee 예제를 활용한 대거의 3가지 초기화

Black-Jin 2019. 9. 16. 01:03
반응형

안녕하세요. 블랙진입니다.


이전부터 대거에 대한 몇가지 포스팅을 했었는데요. 이번에는 지난 포스팅들을 종합하여 step by step 형식으로 기능들을 추가해 대거에 대해 좀더 깊게 알아보고자 합니다.


이전 포스팅을 보고싶으신 분은 아래 링크를 확인해 주세요.


1. DI기본 개념과 Dagger2 사용 예제

2. Dagger2 사용 예제 - 1

3. Dagger2 사용 예제 - 2

4. ContributesInjector 사용해보기




의존성 주입


의존성 주입이란 특정 객체의 인스턴스가 필요한 경우 이를 직접 생성하지 않고 외부에서 생성된 객체를 전달하는 기법입니다. 의존성 주입을 사용하면 다음과 같은 이점을 누릴 수 있습니다.


1. 객체간의 의존성이 없어져 코드 수정이 쉽다. 객체를 생성하는 모듈이 따로 존재하기 때문에 외부에서는 그 객체만 받으면 되며 어떻게 생성 되는지는 신경 쓰지 않아도 됩니다. 


2. 생성한 객체를 쉽게 재사용하고 관리할 수 있다. 객체를 생성하는 작업을 모듈이 전담하게 되므로 객체를 생성하는 방법과 인스턴스 관리를 효율적으로 할 수 있게 됩니다.


3. 객체를  생성하거나 사용할 때 실수를 줄여줍니다. 같은 역활을 하는 객체를 다른 곳에서 별도로 생성하여 사용하게 되면 만일 사용하고 있던 객체가 변경되게 되면 생성하여 사용했던 모든 부분의 코드를 수정해야 됩니다. 이는 작업이 복잡해 지며 실수를 유발할 수 있습니다. 반면에, 의존성 주입을 사용하면 객체를 생성해주는 부분 한 곳만 변경하게 되므로 수정이 간편해집니다. 또한, 해당 객체를 사용하는 모든 부분에 변경 결과가 일괄적으로 적용되므로 변경할 부분을 누락하는 실수를 원천 차단할 수 있습니다.



대거


자바 기반 프로젝트에서 의존성 주입을 사용할 수 있도록 도와주는 라이브러리 중 안드로이드 애플리케이션에서 대거를 많이 사용합니다. 하지만 코틀린이 도입되고 나서는 러닝 커브가 낮은 코인을 사용하기도 하는데요. 이번 포스팅에서는 대거를 주로 다루도록 하겠습니다. 만일 코인이 궁금하신 분은 이 링크를 확인해 주세요.



정의


대거 공식 홈페이지에서는 아래와 같이 설명하고 있습니다.


Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier version created by Square and now maintained by Google.


대거는 자바와 안드로이드를 위한 정적 컴파일 시간에 이뤄지는 의존성 주입 프레임워크 입니다. Square에서 만들었으며 현재는 구글이 유지하고 있습니다.




안드로이드 스튜디오에 대거 적용 하기


코틀린에서 어노테이션 프로세스를 지원하기 위해서는 kapt를 사용해야 합니다. build.gradle 상단에 아래 코드를 추가해 주세요.

apply plugin: 'kotlin-kapt'


안드로이드에서 대거를 사용하기 위한 라이브러리를 dependencies에 추가해 줍니다. 대거 최신 버전은 문서를 확인해 주세요(포스팅 시점 2.22버전이 최신)

//대거 버전
def daggerVersion = '2.22'

//대거의 기본 기능을 사용
implementation "com.google.dagger:dagger:$daggerVersion"
//안드로이드에 특화된 대거의 기능을 사용
implementation "com.google.dagger:dagger-android:$daggerVersion"
//안드로이드 서포트 라이브러리를 지원하는 대거의 기능을 사용
implementation "com.google.dagger:dagger-android-support:$daggerVersion"

//안드로이드에 특화된 대거의 기능을 처리해주는 어노테이션 프로세서
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
//대거의 기본 기능(의존관계 분석 및 코드 생성)을 처리해주는 어노테이션 프로세서
kapt "com.google.dagger:dagger-compiler:$daggerVersion"


여기까지가 준비가 되셨으면 대거 예제로 많이 사용되는 CoffeeMaker를 직접 생성해 보겠습니다.




CoffeeMaker 예제


1. CoffeeMakerModule 생성


모듈은 필요한 객체를 제공하는 역활을 합니다. 우리는 커피 메이커를 만들기 위해 Heater와 Pump 객체를 제공해야 합니다.


Heater

public interface Heater {
void on();

void off();

boolean isHot();
}


A_Heater

public class A_Heater implements Heater {

boolean heating;

public void on() {
this.heating = true;
}

public void off() {
this.heating = false;
}

public boolean isHot() {
return heating;
}
}


Pump

public interface Pump {
void pump();
}


A_Pump

public class A_Pump implements Pump {

private final Heater heater;

A_Pump(Heater heater) {
this.heater = heater;
}

public void pump() {
if (heater.isHot()) {
System.out.println("A_Pump => => pumping => =>");
}
}
}


어노테이션을 사용해 필요한 객체를 모듈에서 제공해 줍니다.

@Module
public class CoffeeMakerModule {

@Provides
Heater provideHeater() {
return new A_Heater();
}

@Provides
Pump providePump(Heater heater) {
return new A_Pump(heater);
}

}



2.  CoffeeComponent 생성


컴포넌트는 모듈에서 제공받은 객체를 조합하여 필요한 곳에 주입하는 역활을 합니다.


CoffeeComponent

@Component(modules = CoffeeMakerModule.class)
public interface CoffeeComponent {

//생성자에 주입
CoffeeMaker coffeeMaker();

//맴버 변수에 주입
void inject(CoffeeMaker coffeeMaker);

}


컴포넌트에서 필요한 곳에 주입하는 방법은 두가지가 있습니다. 하나는 생성자에 주입하는 방법이며 나머지는 맴버 변수에 주입하는 방법입니다.



3. CoffeeMaker 생성


대거를 사용한 주입 방법에는 3가지가 있습니다. 



1. 생성자 주입


CoffeeComponent는 모듈에서 생성한 객체를 어디에 주입할지를 정합니다. 

@Component(modules = CoffeeMakerModule.class)
public interface CoffeeComponent {

//생성자에 주입
CoffeeMaker coffeeMaker();
}


생성자에 @Inject 어노테이션을 사용하여 생성자에 바로 주입해 줄 수 있습니다.

public class CoffeeMaker {

Heater heater;

Pump pump;

@Inject
CoffeeMaker(Heater heater, Pump pump) {
this.heater = heater;
this.pump = pump;
}

public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}


사용예제


프로젝트를 빌드하면 DaggerCoffeeComponent가 생성됩니다. create() 함수를 사용해 생성한 후 CoffeeMaker를 가져오면 됩니다.

CoffeeMaker coffeeMaker = DaggerCoffeeComponent.create().maker();
coffeeMaker.brew();



2. 맴버 변수 주입


CoffeeComponent에서 inject() 함수를 만들어 CoffeeMaker에 주입하겠다고 선언해줍니다.

@Component(modules = CoffeeMakerModule.class)
public interface CoffeeComponent {

//맴버 변수에 주입
void inject(CoffeeMaker coffeeMaker);

}


맴버 변수에 @Inject를 선언해 줍니다.

public class CoffeeMaker {

@Inject
Heater heater;

@Inject
Pump pump;

public CoffeeMaker() {

}

public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}


 사용예제


프로젝트를 빌드하면 DaggerCoffeeComponent가 생성됩니다. create() 함수를 사용해 생성한 후 Component에서 선언헀던 inject() 함수를 사용해 어느 객체에 주입할지 정해줍니다. 

CoffeeMaker coffeeMaker = new CoffeeMaker();

DaggerCoffeeComponent.create().inject(coffeeMaker);
coffeeMaker.brew();


이때는 여러 객체를 만들면 각각의 객체에 주입해서 사용해줘야 합니다. CoffeeMaker 두개 변수를 만들면 아래와 같이 각각 어느 객체의 맴버 변수에 주입해 줄지를 선언해 줍니다.

CoffeeMaker coffeeMaker1 = new CoffeeMaker();
CoffeeMaker coffeeMaker2 = new CoffeeMaker();

DaggerCoffeeComponent.create().inject(coffeeMaker1);
DaggerCoffeeComponent.create().inject(coffeeMaker2);



3. 빌더 패턴 사용


DaggerCoffeeComponent가 만들어지면 builder() 함수를 사용해 모듈을 직접 지정해줄 수 있습니다.

CoffeeComponent coffeeComponent = DaggerCoffeeComponent.builder()
.coffeeMakerModule(new CoffeeMakerModule()).build();


 사용예제


위에서 언급한 두가지 방법으로 사용할 수 있습니다.


- 생성자 주입

CoffeeComponent coffeeComponent = DaggerCoffeeComponent.builder()
.coffeeMakerModule(new CoffeeMakerModule()).build();

coffeeComponent.coffeeMaker().brew();


- 맴버 변수 주입

CoffeeComponent coffeeComponent = DaggerCoffeeComponent.builder()
.coffeeMakerModule(new CoffeeMakerModule()).build();

CoffeeMaker coffeeMaker = new CoffeeMaker();
coffeeComponent.inject(coffeeMaker);
coffeeMaker.brew();




문제 상황 발생


위 예제에서 문제가 발생합니다. 사실 위 코드에서는 A_Pump의 pump()가 제대로 동작하지 않습니다. 이유는 포스팅 하단에 적겠습니다. 이 부분에 대해 한번 생각해보시면 좋을 것 같습니다. 





싱글톤 사용하기


대거에서는 어노테이션을 사용해 인스턴스 관리를 쉽게 처리할 수 있습니다. 대거를 통한 싱글톤 구현법에 대해 보겠습니다.



1) Component에 @SingleTon 추가

@Singleton
@Component(modules = CoffeeMakerModule.class)
public interface CoffeeComponent {

//생성자에 주입
CoffeeMaker coffeeMaker();

//맴버 변수에 주입
void inject(CoffeeMaker coffeeMaker);

}


2) Module에서 싱글톤 객체에 @SingleTon 추가

@Module
public class CoffeeMakerModule {

@Singleton
@Provides
Heater provideHeater() {
return new A_Heater();
}

@Singleton
@Provides
Pump providePump(Heater heater) {
return new A_Pump(heater);
}

}


위와 @SingleTon 어노테이션을 사용하면 A_Heater() 와 A_Pump()는 1개만 생성하여 재사용하게 됩니다.



사용예제


서로 다른 객체 coffeeMaker1과 coffeeMaker2는 같은  A_Heater() 와 A_Pump() 객체를 주입받게 됩니다.

CoffeeMaker coffeeMaker1 = new CoffeeMaker();
CoffeeMaker coffeeMaker2 = new CoffeeMaker();

CoffeeComponent coffeeComponent = DaggerCoffeeComponent.create();

coffeeComponent.inject(coffeeMaker1);
coffeeComponent.inject(coffeeMaker2);

coffeeMaker1.brew();
coffeeMaker2.brew();


coffeeMaker1, coffeeMaker2 라는 엄연히 서로 다른 클래스가 있습니다. 이 클래스 안에는 동일한 Heater와 Pump가 들어가게됩니다. 또한 위에서 발생했던 문제인 pump가 싱글톤으로 만들어 주니 잘 돌아갑니다!




왜? Pump가 동작하게 되었을까?


생성자 주입이 되는 코드입니다. 변수로 넘겨온 heater와 pump에서 pump를 잘 보셔야 합니다. 

@Inject
CoffeeMaker(Heater heater, Pump pump) {
this.heater = heater;
this.pump = pump;
}


pump를 생성할때는 heater를 생성자에 넣어주어야 합니다. 이때 넣어준 heater와 위 코드에서 넣어준 heater는 엄연히 다른 heater입니다. pump 객체 안에 있는 heater와 넘겨온 heater가 다르기 때문에 동작이 안되었던 거고 SingleTon 어노테이션을 붙여준 경우에는 이 둘의 heater가 같기 때문에 동작하게 된 것입니다.



예제에서 사용한 코드는 깃허브에서 보실 수 있습니다.


객체 그래프 


반응형
Comments