DI가 무엇인지 맛만봐보자

DI. 왜이리 어려운거야?

소프트웨어 엔지니어링에서 의존성 주입(Dependency Injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. ‘의존성'은 예를 들어 서비스로 사용할 수 있는 객체이다. 클라이언트가 어떤 서비스를 사용할 것인지 지정하는 대신, 클라이언트에게 무슨 서비스를 사용할 것인지를 말해주는 것이다. ‘주입’은 의존성을 사용하려는 객체로 전달하는 것을 의미한다. 서비스는 클라이언트 상태의 일부이다. 클라이언트가 서비스를 구축하거나 찾는 것을 허용하는 대신 클라이언트에게 서비스를 전달하는 것이 패턴의 기본요건이다

위 인용글은 의존성 주입이라는 키워드로 구글에서 검색했을때 최상단에 나오는 글에서 의존성 주입이라는 단어를 정의한 부분이다. DI가 무엇인지 어느정도 감을 잡은 상태에서 본다고해도 집중해야지 이해할 수 있을 정도인데 처음 공부하는 입장에서는 외계어를 보는 기분이고 공부를 시작하기도전에 흥미가 뚝 떨어져 버릴 것이다.(물론 나도)

이 글은 이러한 분들을 위한 DI 맛보기용 글이 될 것이다. 자 그럼 DI가 무엇인지 맛보러 가보자

우선 DI가 무엇인지 이해라도 해보자

DI는 Dependency Injection(의존성 주입)의 줄임말이다.
단어를 나눠서 생각해보면 다른 것에 의지하여 생활하거나 존재한다는 뜻인 의존성과 어딘가에 넣어줌을 뜻하는 주입 이라는 단어로 쪼개서 생각해볼 수 있다.
단어 뜻으로만해서는 이해가 되지 않을 수 있다. 위 사진 예시로 부가설명을 해보겠다.
알코올 의존은 나 자신이 스스로 알코올에 의존한다는 뜻이지만 주입이라는 단어가 합쳐지면 누군가가 '너는 이제 알코올에 의존하는 사람이 될거야' 라고 말하는 것처럼 알코올에 의존하도록 만들어버리는 것이다.
이처럼 의존성 주입은 '어떤 대상에게 의존성을 집어 넣어버리는 것' 이다.

조금 더 현실성 있는 예시를 보면서 DI에 대해서 조금만 더 깊이 살펴보자

요즘 개발자 채용공고를 보게되면 커피 무제한 제공이 복지로 적혀있는걸 심심찮게 볼 수 있는걸보면 개발자에게 있어서 커피는 떼려야 뗄 수 없는 존재가 되어버렸다. 집중이 필요한 작업이 많다보니 그만큼 개발자는 커피에 의존적 이라는걸 알 수 있다.
즉, 개발자는 커피에 의존성이 생겼다는 것이다. 그러나 모든 개발자가 아메리카노에만 의존한다고는 할 수 없다.
어떤 개발자는 아이스 아메리카노를, 다른 개발자는 아이스 라떼를. 이처럼 각자 원하는 커피가 존재할 것이다.
정리해서 말하면 '개발자가 되면 각자 좋아하는 커피종류에 대한 의존성을 주입받게된다'라는 것이다.

개발자답게 코드로 봐볼까

이번 부분에서는 코드를 작성할것이다. 남의 코드를 볼 생각하니 벌써 머리가 멍할것이다.
허나 돌아가지도 않는 코드일수도있고 클린하지도 않을 코드일것이다.
내가 원하는건 '이 글을 보는 여러분들이 DI가 무엇잊가에대해 감을잡고 개발에 대한 흥미를 잃지 않았으면 좋겠다는 것이다.'

총 4개의 코드블록이 있을예정이며 왜 DI가 나왔는지에 대해서 코드로 설명해보겠다.

개발자가 아침에 일어나서 커피를 사서 마시는 출근을 하는 장면을 코드로 작성해볼것이다.

const wakeUp = async() =>{
  await 핸드폰끄기()
  await 이불정리()
}

const moveTo사무실 = async()=>{
  await 샤워()
  await 대중교통To삼성역()
}

await wakeUp() //출근하기위해 핸드폰을 끄고 이불정리를 할 것이다.
await moveTo사무실() //샤워를하고 대중교통을 이용하여 삼성역으로 이동한다.
await 커피주문()
await 커피마시기()
await 출근및일하기()

정말 심플한 코드를 작성해봤다. 위에서부터 아애로 읽어내려오면되는 구조라 읽기도 편하다. 하지만 문제가있다.
만약 '카페에서 시럽 넣기','카페에서 샷추가','우유를 두유로 변경'과 같이 카페에서 할 수 있는 기능들이 많이 생긴다면 커피를 사는 모든 코드에 위와 같은 함수를 모두 작성해야 할 것이다.
그래서 나온 개념이 Class, 즉 객체지향프로그래밍이다.

export class 커피{
  const 시럽추가하기 =()=>{
    //시럽넣기 로직
  }

  const 샷추가 = () =>{
    //샷 추가 로직
  }
  
  const toMilk =()=>{
    // 우유로 바꾸기 로직
  }
}
-----------------------------------------------------------
 public class 개발자 {
   ... //개발자가 할 수 있는 함수(ex. 일어나기, 대중교통타기, 코딩하기)
   const coffee = new 커피()
 }

이처럼 Class를 만들어서 관리하게되면 유지보수도 쉬울것이고 (ex. 개발자님 카페에서 각얼음을 간얼음으로 변경할 수 있는 기능을 추가해주세요) 코드의 수도 확연하게 줄어들 것이다.
하지만 문제는 또 있다. 만약에 '커피에 여러종류가 있었으면 좋겠어요 예를들면 아메리카노와 카푸치노처럼요'

개발을 하기도전에 머리가 아프다. 분명히 커피 종류에 따라서 당도나 개별 기능이 있을테니 말이다.
이러한 고민을 해결하기위해 상속이라는 개념이 생겼다.

export class 커피{
  const 시럽추가하기 =()=>{
    //시럽넣기 로직
  }

  const 샷추가 = () =>{
    //샷 추가 로직
  }
  
  const toMilk =()=>{
    // 우유로 바꾸기 로직
  }
}
public class Cappuccino extends 커피{...} //커피 클래스를 상속받아 만들어진 카푸치노 클래스
public class Americano extends 커피{...} //커피 클래스를 상속받아 만들어진 아메리카노 클래스

-----------------------------------------------------------
 public class 개발자 {
   ... //개발자가 할 수 있는 함수(ex. 일어나기, 대중교통타기, 코딩하기)
   const coffee = new Cappuccino()
		또는
   const coffee = new Cappuccino()
 }

자 이제 개발자는 출근할때 카페에 들려서 원하는 커피를 주문해서 마실 수 있게되었다.
하지만 역시나 문제가 생길 수 있다. 만약에 신이 개발자를 만들때 '음 이 개발자는 라떼를 좋아하는 개발자로 만들어야지' , '이 개발자는 아메리카노를 좋아하는 개발자로 만들어야지' 라고 고민을 할 수 있다.
하지만 걱정이없다. 우리에겐 위처럼 상속이라는 기가막힌 방법을 알고있으니깐.

...
public class Cappuccino extends 커피{...} //커피 클래스를 상속받아 만들어진 카푸치노 클래스
public class Americano extends 커피{...} //커피 클래스를 상속받아 만들어진 아메리카노 클래스

-----------------------------------------------------------
 public class 카푸치노개발자 {
   ... //개발자가 할 수 있는 함수(ex. 일어나기, 대중교통타기, 코딩하기)
   const coffee = new Cappuccino()
 }
 
 public class 아메리카노개발자 {
   ... //개발자가 할 수 있는 함수(ex. 일어나기, 대중교통타기, 코딩하기)
   const coffee = new Americano()
 }
 
 -----------------------------------------------------------
 public class 개발자만들기{
   const 이관형 = new 아메리카노개발자()
   const foo = new 카푸치노개발자()
 }

짜잔 완벽하게 만들어냈다.

????????????????????
..만들어야할 개발자 Class가 30개가 넘어보인다.

이러한 불편함을 해소시키기위해서 DI가 등장한것이다. 우리 예제로 돌아와서 쉽게 설명하면 신이 개발자를 만들때 '카푸치노를 좋아하는 개발자'와 '아메리카노를 좋아하는 개발자'에서 선택하는게 아니라
개발자를 만들면서 '너는 카푸치노를 좋아한다'라고 최면을 건다고 할 수 있다.

...
public class Cappuccino extends 커피{...} //커피 클래스를 상속받아 만들어진 카푸치노 클래스
public class Americano extends 커피{...} //커피 클래스를 상속받아 만들어진 아메리카노 클래스

-----------------------------------------------------------
 public class 개발자 {
   private readonly coffee:Coffee
   constructor (_coffee:Coffee) { //생성자를 이용해서 좋아하는 커피를 주입받게된다.
     this.coffee = _coffee
   }
 }
 
 -----------------------------------------------------------
 public class 개발자만들기{
   const americanoCoffee = new Americano()
   const 이관형 = new 개발자(americanoCoffee) //이관형은 이제 아메리카노를 좋아하는 개발자다.
 }

이처럼 개발자를 만들때 커피종류를 주입시켜줌으로써 커피 종류가 아무리 많아져도 개발자 클래스는 하나가 된다.

글을 마치며

모든 개발방법론(ex. 객체지향, DI...)은 프로그래밍을 함에있어서 불편함을 해소시키기 위해서 나온 것이다.
그렇기때문에 이전의 어떠한 불편함을 해소시키기위해서 생긴것인지 시작해서 조급하지말고 천천히 이해하다보면 자연스럽게 '아하' 하고 이해할 수 있을것이다.

좋은 웹페이지 즐겨찾기