의존성 주입(Dependency Injection)이라는 용어는 개발 공부를 하면서 많이 들어봤을 것이다. 필자 또한 많이 들어본 단어이고, 어떤 말인지 대충 알고 있던 개념이다. 하지만 프로젝트를 진행하면서 각 객체간 의존성이 증가하였고, 이에 따라 의존성 주입에 대해 더 학습하고 싶어 학습한 내용을 작성해보고자 한다.
먼저 의존관계 주입을 설명하기 전, "의존관계"라는 단어에 대하여 설명하고자 한다.
A와 B의 관계에서 B가 A를 의존한다고 하자.

B가 A를 의존하고 있다. 그 말은 즉시 A가 바뀌면 B에 영향이 미칠 수 있다는 뜻이다.
class Drive {
private val electricCar: ElectricCar
init {
electricCar = ElectricCar()
}
}
현재 Drive 클래스는 ElectricCar 클래스에 의존하고 있다. 이러한 방식은 객체 지향 설계의 원칙 중 DIP를 위반한다. 추상(인터페이스)에 의존하는것이 아닌, 구현(클래스)에 의존하고 있기 때문이다. 또한 다른 차에 의존하려면 Drive클래스에 있는 "코드"를 변경하여야 하고, 그것 또한 구현에 의존하기 때문에 DIP를 위반한다.
class Drive {
// private ElectricCar electricCar
private val hybridCar: HybridCar
init {
// electrnicCar = new ElectricCar()
hybridCar = HybridCar()
}
}
이런식으로 Car 클래스 내부의 코드를 수정하여야 한다. 이 코드는 DIP를 위반한다.
인터페이스를 사용하여 "역할"과 "구현"을 분리하여 설계해보자.
interface Car {
fun drive()
}
class ElectricCar : Car {
override fun drive() {
println("Drive ElectricCar")
}
}
class HybridCar : Car {
override fun drive() {
println("Drive HybridCar")
}
}
Car 인터페이스를 생성하고 ElectricCar, HybridCar 클래스가 Car 인터페이스를 구현하였다.
class Driver(private val car: ElectronicCar) {
fun start() {
car.drive()
}
}
이제 운전자 클래스를 생성하고, Car를 구현한 인터페이스 중 전기차를 의존하였다. DIP를 만족하는가 ?
답은 아니다. 이유는 Car라는 추상(인터페이스)에 의존하면서 ElectricCar라는 구현(클래스)에도 같이 의존하고 있기 떄문이다. DIP를 지키기 위해서는 누군가 객체를 외부에서 주입해줘야 한다.
class Driver(private val car: Car) {
fun start() {
car.drive()
}
}
코드를 보면 car에만 의존을 한 상황이고, 생성자를 통하여 외부에서 객체를 주입받고 있다.
따라서 우리는 만약 차의 종류를 변경하려고 하면 Driver 클래스의 코드를 변경하는것이 아닌, 외부에서 주입하는 코드만 변경하면 된다.
이렇게 외부에서 의존관계를 결정 후 주입하는 방식을 "의존관계 주입" 이라고 한다. 현재 클래스에서는 "구현"에만 신경을 쓰고, Car 인터페이스를 구현한 다양한 클래스들 중, 어떤 클래스의 객체가 들어올지는 모른다. 현재 Driver 클래스는 DIP를 잘 지키면서, "역할"과 "구현"을 분리하여 "구현"에만 신경쓰면 된다.
안드로이드에서는 AppContainer를 이용하여 의존성을 관리할 수 있다. 하지만 앱의 규모가 커질수록 보일러플레이트 코드가 늘어나고, 컨테이너가 필요하지 않을 때 삭제 등 컨테이너의 수명 주기를 직접 관리해야 하는 번거로움이 있다. 따라서 다음 포스트는 Hilt 라이브러리를 이용하여 손쉽게 안드로이드에서 의존성 주입을 구현하는 방법을 설명하겠다.
수정사항 언제든 환영입니다.