요즘들어 삘을 받아서 폭풍 포스팅 중 입니다.🤗
이번 포스팅은 DI(Dependency Injection) - 의존성 주입에 대한 내용인데요.
먼저 DI가 무엇인지 알아보고 안드로이드에서 많이 쓰이는 DI 라이브러리들을 간단히 무엇이 있는지 알아보도록 하겠습니다.
자 먼저 의존성이란것을 알아보도록 합시다.
(1) 의존성이란?
가령 직장인이 출근을 할때 직장이 멀다면 교통수단에 의존해서 출근을 해야합니다.
프로그래밍에서도 의존은 뜻이 같습니다. 의존성은 함수에 필요한 클래스 또는 참조변수나 객체에 의존하는 것이라고 볼 수 있습니다.
(2) 주입이란?
그렇다면 위의 함수에 필요한 클래스 또는 참조변수나 객체 같은 것들은 어떻게 생성될까요?
직접 생성하거나 누군가 생성해준 것을 이용하는 방법이 있지만 이럴때 DI(의존성 주입)를 사용합니다. 보통은 내부에서 필요한 내용(객체)을 생성하여 참조/사용을 내부가 아니라 외부에서 객체를 생성해서 넣어주는 것을 주입한다고 합니다. 그렇기 때문에 개발자들이 객체를 생성하는 번거로움과 다양한 테스트 케이스를 고려하는 경우를 줄이고 변수 사용과 개발에 더욱이 집중할 수 있게 해주며 클래스간의 결합도(coupling)를 낮추어 의존성을 줄입니다.
의존성 주입이란 정리하면 이렇습니다.
자 그럼 장점 또한 정리해보겠습니다.
다만 단점도 있습니다.
자신이 처음부터 개발했던 프로젝트라면 모르겠으나 중간에 투입이되어 소스를 파악(Reading)해야하는 상황이라면 소스를 처음보기 때문에 코드를 추적하고 읽기가 어렵습니다.
하지만 장점이 더 많고 일단 유지보수하기가 용이해지므로 단점은 넘어가도록 합시다😜
저는 어릴때부터 게임을 엄청좋아해서 간단하게 게임에 빗대어 예를 들어보겠습니다.
가령 "동물의 숲"이라는 게임에서 쇠로된 도끼, 쇠로된 삽을 만드려면 철광석이 필요합니다.
각 도구별로 공통적으로 철광석이 1개씩 있어야 합니다.
코드로 표현하면 아래와 같습니다.
// 철광석 클래스
class IronNugget {
...
}
// Axe클래스 내부에서 IronNugget 클래스를 생성하여 사용
class Axe {
val ironNugget = IronNugget()
...
}
// Shovel 클래스 내부에서 IronNugget 클래스를 생성하여 사용
class Shovel {
val ironNugget = IronNugget()
...
}
이렇게 되면 의존성이 생기게 되는데요 도끼(Axe)라는 클래스가 내부에서 철광석(IronNugget)이라는 클래스를 참조하는 경우 도끼 클래스 -> 철광석 클래스 형식으로 의존성을 갖는다고 말할 수 있습니다.
삽(Shovel) 클래스도 마찬가집니다.
근데 이렇게 의존성이 생기게 되면 철광석 클래스의 변화가 생기면 의존성을 갖는 도끼 클래스도 같이 변경을 해야하는 문제가 발생합니다.
만약 철광석 클래스가 바뀌면 철광석 클래스의 의존성을 갖는 모든 클래스가 변경이 되어야 합니다.
애플리케이션의 규모가 작아서 철광석 클래스에 의존성을 갖는 클래스가 한 두개라면 상관이 없겠지만 애플리케이션의 규모가 커지면서 점점 의존성을 갖는 클래스가 많아지면 유지보수시 개판5분전이 되기 시작합니다. 🔥🔥🔥🧑🏻💻신난다! 워라밸 끝!
만약 철광석 클래스에 의존성이 생긴다고 가정해본다면 아래와 같이 생성자를 추가하여 바뀌어야 합니다.
예시 코드로 확인해보겠습니다.
// 바위 클래스
class Rock {
...
}
// 철광석 클래스
class IronNugget(val rock: Rock) { {
...
}
// Axe클래스 내부에서 IronNugget 클래스를 생성하여 사용
class Axe {
val ironNugget = IronNugget(Rock()) // <-- 변경됨
...
}
// Shovel 클래스 내부에서 IronNugget 클래스를 생성하여 사용
class Shovel {
val ironNugget = IronNugget(Rock()) // <-- 변경됨
...
}
저는 클래스 생성에서 의존성을 주입을 했습니다.
즉, 철광석(IronNugget) 클래스에 바위(Rock) 클래스에 대한 의존성을 생성자를 이용하여 주입(Injection)했습니다.
의존하는 클래스를 직접 생성하는 것이 아닌, 주입해줌으로써 객체 간의 결합도를 줄이고 좀 더 유연한 코드를 작성할 수 있게됩니다.
이때 외부에서 객체를 관리하게 되는데 이를 IOC(inversion of Control, 제어의 역전)라 합니다.
IOC는 객체의 생성부터 생명주기(LifeCycle) 관리까지 컨테이너에 의해 제어 되는 것을 의미하며 의존성 주입(DI)은 객체간의 의존성을 자기 자신이 아닌 외부에서 주입받는 개념입니다.
"외부에서 제어 및 객체간의 의존성을 최소화 한다"
여기서 무엇인가 눈치 챈 분들이 계실텐데 그렇습니다.
아키텍쳐 패턴들(MVP, MVM, MVVM)에서 중점적으로 보이는 장점중의 하나입니다.
그래서 해당 모델들을 구현할때는 DI 라이브러리를 사용하는 것이 개발함에 있어 이제는 필수중의 필수가 되었습니다.
자 우리가 누굽니까?
귀찮은것을 극도로 싫어하는 개발자들 입니다.
이를 간단히 해결하려면 어떻게 해야 할까요??
잘 만들어진 DI 라이브러리를 사용하는겁니다.
대표적인 안드로이드 DI 라이브러리는 다음과 같습니다.
DI 또는 DI 라이브러리를 사용하면 관심사를 분리 했으므로 객체를 생성하는 부분과 아닌 부분이 나뉘어지게 되며 클래스간의 결합도가 크게 줄어들어 의존성도 줄어들게 되니 리팩토링 및 유닛테스트가 쉬워집니다. 또한 주입되는 모듈 코드를 재사용할 수 있으니 보일러 플레이트 코드도 줄어들게 됩니다.
그래서 이래저래 장점이 더 많다보니 요즘은 앱 개발시 필수라고 할 수 있겠습니다.
참고 내용
https://ko.wikipedia.org/wiki/의존성_주입
https://medium.com/@jang.wangsu/di-dependency-injection-이란-1b12fdefec4f
https://velog.io/@wlsdud2194/what-is-di
https://www.charlezz.com/?p=1259
https://jungwoon.github.io/android/2019/08/21/Koin/
도움이 많이 된 도서
아키텍처를 알아야 앱 개발이 보인다, 옥수환 저 | 비제이퍼블릭(BJ퍼블릭)
비유도 너무 좋고 좋은 글 감사드립니다!! 저에게는 koin도 가파른 러닝커프인데
좋은 글 덕분에 도움 많이 받았네요 ㅎㅎ