본문 바로가기

Android

CoffeeMaker 예제로 알아보는 Dagger

Part1. CoffeeMaker 예제로 알아보는 Dagger

Dagger 예제로 흔히 접할 수 있는 CoffeeMaker 예제를 살펴보고 Dependency Injection을 알아본다.

Step1.완성품 CoffeeMaker

001완성형커피메이커

보통 우리가 커피메이커를 사용할 때 작동버튼 하나만 알면 커피를 만들 수 있다. 기계 내부에서 어떻게 물이 데워지고 커피가 추출되는지는 모르지만 Brew버튼 하나만 알면 커피메이커를 사용할 수 있다.

이를 코드로 표현해보면

002완성형커피메이커코드

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

커피메이커 객체를 생성하지만 커피메이커 내부에 사용되는 히터와 펌프는 외부에 노출되지 않는다.

Step2. 외장형 부품을 사용하는 CoffeeMaker

나는 커피메이커를 조금 더 효율적으로 사용하고 싶어서 제품을 변경하기로 했다. 히터와 펌프를 외장형으로 지원하는 커피메이커로 선택했다.

그림으로 표현하면

004

코드로 표현해보면

003

펌프는 히터에 의해서 물이 끓고 있어야 작동할 수 있다.

005

Heater heater = new Heater();
Pump pump = new Pump(heater);
coffeeMaker = new CoffeeMaker(heater, pump);

이제 커피메이커를 사용하기 위해 별도의 히터와 펌프를 준비하고 연결해줘야한다.

Step3. 더 성능이 좋은 히터 부품을 연결해보자.

외장형 커피메이커로 바꿨으니 조금 더 성능이 좋은 히터로 교체해서 사용해보자. 히터는 가스히터와 전기히터 2가지 옵션이 있는데 가스히터가 더 성능이 좋다고 가정한다.

006

Heater라는 공통의 interface를 구현한 GasHeaterElectricHeater 중 선택할 수 있다.

007008

커피메이커가 GasHeaterElectricHeater 부품을 번갈아 사용하며 잘 작동하는 것을 확인할 수 있다.

이제 커피메이커는 더 효율이 좋은 부품을 가져와서 사용할 수도 있고 고장이 났을 경우 Heater, Pump 중 부분만 교체하여 수리할 수도 있을 것이다.

Step4. 관리자 채용하기

외장형 커피메이커로 교체한 후 문제점이 발생했다. 나는 커피를 만드는 일에 집중하고 싶은데 Heater와 Pump의 관리까지 직접 챙겨야했기 때문이다. 이 문제를 해결하기 위해 히터와 펌프의 관리를 도맡아줄 관리자를 두기로 했다.

Injection이라는 관리자를 만들자.

009

인젝션이라는 부품관리를 전담하는 관리자를 채용했다. 이제 부품은 인젝션이 관리하고 나는 필요할 때 부품을 사용해서 커피를 만들 것이다.

다시 커피를 만들어보자.

010

문제가 발생해서 Brewing에 실패했다. 히터가 물을 충분히 끓이지 못해서 펌프가 작동하지 못했다. 원인은 하나의 Heater 인스턴스가 PumpCoffeeMaker동시에 열을 전달하지 못하고 각기 다른 인스턴스가 사용되어었다.

아직 관리자가 부품을 다루는 법을 제대로 익히지 못해서 생긴 문제였다. 관리자에게 히터를 제대로 연결하는 방법을 다시 교육해보자.

011

provideHeater()가 일관되게 하나의 히터(인스턴스)를 사용할 수 있도록 코드를 수정한다. 지금부터 새롭게 교육받은 인젝션 관리자는 Heater를 제공할 때 항상 동일한 인스턴스를 제공할 것이다.

결과를 확인해보자.

012

예상대로 히터가 펌프에 열을 잘 전달하고 제대로 커피가 추출되었다.

Injection.provideCoffeeMaker().brew();

이제 나는 커피메이커를 사용하고 싶을 때 Injection관리자에게 인스턴스 제공요청을 하고 Brew버튼만 눌러서 커피를 내리는 일에만 집중할 수 있다.

관리자를 채용함으로서 외장형 부품을 채용하고도 Step1과 같이 커피내리는 일에만 집중할 수도 있게 됐다.

Step5. Dagger는 매우 능력 있는 관리자이다.

이제 Dagger라는 인젝션 관리를 위해 태어난 전문 툴을 알아보자.

처음 Dagger를 공부할 때 가장 어려웠던 부분은 @Component, @Module, @Inject, @Provides, @Scope, @Binds 등 어노테이션들이 한꺼번에 등장해서 각 역할을 제대로 파악하기 힘들었다. 그리고 튜토리얼마다 사용하는 법이 조금씩 달라서 더욱 개념을 잡기 어렵게 했다.

오늘은 CoffeeMaker를 작동시킬만큼의 의존성관리만 제공하는 간단한 Dagger를 알아보고자 한다.

  1. Dagger는 @Inject@Provides 어노테이션이 없이는 인젝션을 할 수 없다.
  2. @Module은 인젝션할 객체를 생성, 제공하는 역할이다.
  3. @Component@Inject@Module 연결하는 다리 역할이다.

먼저 인스턴스를 제공하는 @Module 클래스를 생성한다.

014

@Module 어노테이션을 가지고 있는 클래스이며 HeaterPump를 제공하는 역할이다.

015

다음으로 CoffeeMaker의 생성자에 @Inject 어노테이션을 추가한다. 생성자의 파라미터로 필요한 HeaterPump를 제공받아서 주입해준다. 그리고 CoffeeMaker의 인스턴스도 제공가능하도록 Dagger에게 알려준다.

Constructor Injection은 생성자에 @Inject 어노테이션을 추가하는 것으로 Dagger에게 해당 객체를 어떻게 생성할 수 있는지를 알려주는 과정이다. Dagger는 필요할 때 이 객체를 생성하고 제공할 수 있게 된다.

Dagger를 적용 후 실행해보면,

016

Step4의 초기와 같이 히터가 제대로 작동하지 못하는 현상이 발생한다. 마찬가지로 동일한 히터의 인스턴스가 사용된 것이 아니라서 생기는 문제이다.

인스턴스의 생명주기를 관리하기 위해서 Scope가 필요하다. Dagger에게 해당 인스턴스의 생성, 관리를 맡길 때 이 객체의 인스턴스를 하나만 생성할지, 중복 생성할지 또는 소멸 시킬지 알려주어야한다.

여기서는 하나의 히터를 사용할 것이고 동일한 히터 인스턴스가 제공되어야 하므로 @Singleton 어노테이션을 사용한다.

017

@Singleton 스코프를 적용해서 Heater Instance를 관리하게 한 뒤 히터와 펌프가 올바르게 동작하는 것을 확인할 수 있다.

018

@Component는 Dagger가 프로젝트에서 어떻게 Injection을 할지 메소드 명세를 가지고 있는 지침서라고 볼 수 있다. Dagger는 컴파일을 통해 DaggerComponentName형태의 구현체를 생성하고 @Inject요청이 있는 곳에 @Module의 제공을 받아서 인스턴스를 주입한다.

Step6. 커피메이커의 생성자를 변경하자.

// 커피메이커의 생성자
@Inject
public CoffeeMaker(Heater heater, Pump pump) {
    //...
}

커피메이커의 생성자를 보면 여전히 외부에서 Heater와 Pump를 생성후 CoffeeMaker의 생성에 사용이 된다. 이는 커피메이커를 생성하기 위해서 히터와 펌프 인스턴스가 먼저 생성되어야 커피메이커 생성을 진행할 수 있다는 의미이다.

// Step1의 커피메이커의 생성자
public CoffeeMaker() {
    heater = new Heater();
    pump = new Pump(heater);
}

커피메이커 생성자에서 파라미터를 없애고 외부에서 히터와 펌프를 연결하는 형태로 변경해보자. 이는 히터와 펌프가 준비되기 전에 커피메이커를 준비시키고 나중에 히터와 펌프를 연결 시킬 수 있다는 의미이다.

019

@Inject Heater heater;
@Inject Pump pump;

@Inject
public CoffeeMaker() { }

HeaterPump는 Dagger가 어떻게 생성하는지 알고있는 객체들이다.(@Module에 제공법 적혀있음) Member Injection을 통해서 @Inject 어노테이션이 있는 멤버변수에 객체 주입이 요청되고 @Component는 해당 객체를 제공(주입)한다.

CoffeeMaker coffeeMaker = new CoffeeMaker(); // 먼저 생성 DaggerCoffeeMakerManager.create().inject(coffeeMaker) // 외장형 부품 연결 coffeeMaker.brew(); // 커피 추출시작

이제 커피메이커의 생성자에는 파라미터가 없다. 히터와 펌프가 없이도 커피메이커의 인스턴스는 생성될 수 있다.(독립적으로 인스턴스 생명주기를 관리할 수 있다.) 단지 Brew()을 위해서는 외장형 부품인 히터와 펌프가 연결되어야한다는 점이다.

Summary

이제 Dagger라는 능력이 뛰어난 관리자를 채용했다. 나는 커피메이커 사용에만 집중하고 커피메이커 관리는 Dagger에게 모두 맡겼다.

  • 커피메이커의 성능을 높이고 싶다.
  • 커피메이커의 부품을 외장형으로 바꾸고 직접 커스텀하도록 변경한다.
  • 커피메이커의 외장형 부품을 담당할 담당자(Dagger)를 둔다.
  • 이제 커피메이커의 각 부품을 원하는대로 바꿔가며 사용할 수 있다.

번거롭게 외장형 부품을 사용하는 이유는 각 파트간에 결합을 느슨하게 해서 테스트유지보수하기 쉽도록하는 것이 최종 목표이다. 처음 대거를 사용하는 법을 익히는 것은 복잡하지만 대거를 통해서 더 쉬운 테스트 코드를 작성하고 문제가 생긴 부분을 쉽게 교체할 수 있게 된다.