Part1. CoffeeMaker 예제로 알아보는 Dagger
Dagger 예제로 흔히 접할 수 있는 CoffeeMaker 예제를 살펴보고 Dependency Injection을 알아본다.
Step1.완성품 CoffeeMaker
보통 우리가 커피메이커를 사용할 때 작동버튼 하나만 알면 커피를 만들 수 있다. 기계 내부에서 어떻게 물이 데워지고 커피가 추출되는지는 모르지만 Brew버튼 하나만 알면 커피메이커를 사용할 수 있다.
이를 코드로 표현해보면
CoffeeMaker coffeeMaker = new CoffeeMaker(); coffeeMaker.brew();
커피메이커 객체를 생성하지만 커피메이커 내부에 사용되는 히터와 펌프는 외부에 노출되지 않는다.
Step2. 외장형 부품을 사용하는 CoffeeMaker
나는 커피메이커를 조금 더 효율적으로 사용하고 싶어서 제품을 변경하기로 했다. 히터와 펌프를 외장형으로 지원하는 커피메이커로 선택했다.
그림으로 표현하면
코드로 표현해보면
펌프는 히터에 의해서 물이 끓고 있어야 작동할 수 있다.
Heater heater = new Heater();
Pump pump = new Pump(heater);
coffeeMaker = new CoffeeMaker(heater, pump);
이제 커피메이커를 사용하기 위해 별도의 히터와 펌프를 준비하고 연결해줘야한다.
Step3. 더 성능이 좋은 히터 부품을 연결해보자.
외장형 커피메이커로 바꿨으니 조금 더 성능이 좋은 히터로 교체해서 사용해보자. 히터는 가스히터와 전기히터 2가지 옵션이 있는데 가스히터가 더 성능이 좋다고 가정한다.
Heater
라는 공통의 interface를 구현한 GasHeater
와 ElectricHeater
중 선택할 수 있다.
커피메이커가 GasHeater
와 ElectricHeater
부품을 번갈아 사용하며 잘 작동하는 것을 확인할 수 있다.
이제 커피메이커는 더 효율이 좋은 부품을 가져와서 사용할 수도 있고 고장이 났을 경우 Heater, Pump 중 부분만 교체하여 수리할 수도 있을 것이다.
Step4. 관리자 채용하기
외장형 커피메이커로 교체한 후 문제점이 발생했다. 나는 커피를 만드는 일에 집중하고 싶은데 Heater와 Pump의 관리까지 직접 챙겨야했기 때문이다. 이 문제를 해결하기 위해 히터와 펌프의 관리를 도맡아줄 관리자를 두기로 했다.
Injection
이라는 관리자를 만들자.
인젝션이라는 부품관리를 전담하는 관리자를 채용했다. 이제 부품은 인젝션이 관리하고 나는 필요할 때 부품을 사용해서 커피를 만들 것이다.
다시 커피를 만들어보자.
문제가 발생해서 Brewing에 실패했다. 히터가 물을 충분히 끓이지 못해서 펌프가 작동하지 못했다. 원인은 하나의 Heater
인스턴스가 Pump
와 CoffeeMaker
에 동시에 열을 전달하지 못하고 각기 다른 인스턴스가 사용되어었다.
아직 관리자가 부품을 다루는 법을 제대로 익히지 못해서 생긴 문제였다. 관리자에게 히터를 제대로 연결하는 방법을 다시 교육해보자.
provideHeater()
가 일관되게 하나의 히터(인스턴스)를 사용할 수 있도록 코드를 수정한다. 지금부터 새롭게 교육받은 인젝션 관리자는 Heater
를 제공할 때 항상 동일한 인스턴스를 제공할 것이다.
결과를 확인해보자.
예상대로 히터가 펌프에 열을 잘 전달하고 제대로 커피가 추출되었다.
Injection.provideCoffeeMaker().brew();
이제 나는 커피메이커를 사용하고 싶을 때 Injection관리자에게 인스턴스 제공요청을 하고 Brew
버튼만 눌러서 커피를 내리는 일에만 집중할 수 있다.
관리자를 채용함으로서 외장형 부품을 채용하고도 Step1
과 같이 커피내리는 일에만 집중할 수도 있게 됐다.
Step5. Dagger는 매우 능력 있는 관리자이다.
- Build.gradle에 dagger 라이브러리 추가하기. Github: dagger
이제 Dagger라는 인젝션 관리를 위해 태어난 전문 툴을 알아보자.
처음 Dagger를 공부할 때 가장 어려웠던 부분은 @Component
, @Module
, @Inject
, @Provides
, @Scope
, @Binds
등 어노테이션들이 한꺼번에 등장해서 각 역할을 제대로 파악하기 힘들었다. 그리고 튜토리얼마다 사용하는 법이 조금씩 달라서 더욱 개념을 잡기 어렵게 했다.
오늘은 CoffeeMaker
를 작동시킬만큼의 의존성관리만 제공하는 간단한 Dagger를 알아보고자 한다.
- Dagger는
@Inject
나@Provides
어노테이션이 없이는 인젝션을 할 수 없다. @Module
은 인젝션할 객체를 생성, 제공하는 역할이다.@Component
는@Inject
을@Module
연결하는 다리 역할이다.
먼저 인스턴스를 제공하는 @Module 클래스를 생성한다.
@Module
어노테이션을 가지고 있는 클래스이며 Heater
와 Pump
를 제공하는 역할이다.
다음으로 CoffeeMaker의 생성자에 @Inject
어노테이션을 추가한다. 생성자의 파라미터로 필요한 Heater
와 Pump
를 제공받아서 주입해준다. 그리고 CoffeeMaker
의 인스턴스도 제공가능하도록 Dagger에게 알려준다.
Constructor Injection은 생성자에
@Inject
어노테이션을 추가하는 것으로 Dagger에게 해당 객체를 어떻게 생성할 수 있는지를 알려주는 과정이다. Dagger는 필요할 때 이 객체를 생성하고 제공할 수 있게 된다.
Dagger를 적용 후 실행해보면,
Step4
의 초기와 같이 히터가 제대로 작동하지 못하는 현상이 발생한다. 마찬가지로 동일한 히터의 인스턴스가 사용된 것이 아니라서 생기는 문제이다.
인스턴스의 생명주기를 관리하기 위해서 Scope가 필요하다. Dagger에게 해당 인스턴스의 생성, 관리를 맡길 때 이 객체의 인스턴스를 하나만 생성할지, 중복 생성할지 또는 소멸 시킬지 알려주어야한다.
여기서는 하나의 히터를 사용할 것이고 동일한 히터 인스턴스가 제공되어야 하므로 @Singleton
어노테이션을 사용한다.
@Singleton
스코프를 적용해서 Heater Instance를 관리하게 한 뒤 히터와 펌프가 올바르게 동작하는 것을 확인할 수 있다.
@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);
}
커피메이커 생성자에서 파라미터를 없애고 외부에서 히터와 펌프를 연결하는 형태로 변경해보자. 이는 히터와 펌프가 준비되기 전에 커피메이커를 준비시키고 나중에 히터와 펌프를 연결 시킬 수 있다는 의미이다.
@Inject Heater heater;
@Inject Pump pump;
@Inject
public CoffeeMaker() { }
Heater
와 Pump
는 Dagger가 어떻게 생성하는지 알고있는 객체들이다.(@Module
에 제공법 적혀있음) Member Injection을 통해서 @Inject
어노테이션이 있는 멤버변수에 객체 주입이 요청되고 @Component
는 해당 객체를 제공(주입)한다.
CoffeeMaker coffeeMaker = new CoffeeMaker(); // 먼저 생성 DaggerCoffeeMakerManager.create().inject(coffeeMaker) // 외장형 부품 연결 coffeeMaker.brew(); // 커피 추출시작
이제 커피메이커의 생성자에는 파라미터가 없다. 히터와 펌프가 없이도 커피메이커의 인스턴스는 생성될 수 있다.(독립적으로 인스턴스 생명주기를 관리할 수 있다.) 단지 Brew()을 위해서는 외장형 부품인 히터와 펌프가 연결되어야한다는 점이다.
Summary
이제 Dagger라는 능력이 뛰어난 관리자를 채용했다. 나는 커피메이커 사용에만 집중하고 커피메이커 관리는 Dagger에게 모두 맡겼다.
- 커피메이커의 성능을 높이고 싶다.
- 커피메이커의 부품을 외장형으로 바꾸고 직접 커스텀하도록 변경한다.
- 커피메이커의 외장형 부품을 담당할 담당자(Dagger)를 둔다.
- 이제 커피메이커의 각 부품을 원하는대로 바꿔가며 사용할 수 있다.
번거롭게 외장형 부품을 사용하는 이유는 각 파트간에 결합을 느슨하게 해서 테스트와 유지보수하기 쉽도록하는 것이 최종 목표이다. 처음 대거를 사용하는 법을 익히는 것은 복잡하지만 대거를 통해서 더 쉬운 테스트 코드를 작성하고 문제가 생긴 부분을 쉽게 교체할 수 있게 된다.
'Android' 카테고리의 다른 글
Fragment에 리스트 출력하기 Part.1 (0) | 2019.09.29 |
---|---|
ViewModel Testing 코드 작성하기 (0) | 2019.09.18 |
Room Database Testing Code 작성하기 (0) | 2019.09.07 |
실제로 앱을 개발하면서 안드로이드 프로그래밍 배우자. (0) | 2019.08.08 |
최소 기능 제품, Minimum Viable Product(MVP) (0) | 2019.08.08 |