의존성 주입 - Dependency Injection

Hunter Joe·2025년 4월 22일

📌 의존성 주입(DI)이란?

의존성 주입을 한 문장으로 표현하면 다음과 같다.

의존성 주입이란 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉 - wikipedia

의 문장을 표현만 다르게해서 말하면

1. "의존성 주입이란 객체 A가 필요로하는 객체 B를 A스스로 만들지 않고 외부에서 B를 만들어서 A에게 전달(주입)해주는 방식이다."

2. "의존성 주입이란 외부에서 B를 만들어서 A에게 전달(주입)해주는 방식이다."

위 두 문장을 그림으로 나타내면 다음과 같다.

이제 의존성 주입이라는 단어는 확실하게 알았다면

기존 프로그래밍의 어떤 방식이 불편했기에 이런 설계 패턴이 나타났는지 그리고 의존성 주입 패턴을 사용하게 되면 장점이 뭔지 알아보자

📌 의존성 주입 이전의 삶은 어땠나요?

의존성 주입 패턴이 나타나게된 배경에 대한 설명

원문 - Quora
의존성 주입은 2000년대 초반
Martin Flowler,
Rod Johnson- Spring 프레임워크 창시자
같은 인물들에 의해 널리 알려졌어.

당시 Weblogic, Websphere, JBoss 같은 애플리케이션 서버(Application Server)가 주류였던 시대야.
이런 서버들은 EJB(Enterprise JavaBeans) 명세를 따라야 했고,
조심하지 않으면 애플리케이션이 그 서버에서만 실행될 수 있는 형태로 작성되곤 했어.

다시 말하면, 유닛 테스트(unit test)는 사실상 불가능했어.
유닛 테스트를 하려면 애플리케이션 서버 안에서 실행해야 했는데,
그렇게 하면 그건 더 이상 유닛 테스트가 아니지.

당시 많은 개발자들은 유닛 테스트를 아예 작성하지 않고,
코드를 애플리케이션 서버에 배포한 뒤 수동으로 테스트하는 것에 익숙했어.

그래서 유닛 테스트를 작성하지 않는 이상,
의존성 주입의 필요성을 느낄 일이 거의 없었어.
대부분은 객체를 그냥 직접 생성하고,
인터페이스나 목(mock) 구현 같은 건 신경도 쓰지 않고 구체 클래스(concrete class)를 바로 사용했지.

그러다 2000년대 초반,
유닛 테스트 문화(unit testing movement)가 본격적으로 확산되기 시작했고,
개발자들은 더 이상 긴 코드/테스트/배포 사이클을 가진 애플리케이션 서버를 좋아하지 않게 됐어.

이때 등장한 Spring 프레임워크는 매우 가볍고,
핵심에 의존성 주입(DI) 개념을 담고 있었기 때문에 빠르게 인기를 끌었지.

Spring 덕분에 모의(mock) 객체를 활용한 테스트가 쉬워졌고,
실제 운영 환경에서는 진짜 구현체로 쉽게 교체할 수 있게 되었어.

함께 등장한 도구들도 있었지:
JUnit, Mock 객체, Mockito 같은 유닛 테스트 도구들이 함께 성장했어.

오늘날에는 테스트 가능성(Testability)과 유닛 테스트가
건전한 소프트웨어 엔지니어링의 핵심 원칙으로 자리잡았고,
CI/CD, DevOps 같은 현대 개발 흐름에서도 그 중요성이 확고해졌지.

하지만 이 모든 것의 뿌리는 겨우 20년 전으로 거슬러 올라가는 비교적 최근의 일이라는 거야.


정리해보면 2000년대 초반 유닛 테스트 문화가 본격적으로 확산되었고,
개발 사이즈가 커지다 보니 개발자들은 더 이상 긴코드, 테스트, 배포 사이클이 덩어리로 되어 있는 애플리케이션 서버를 싫어하게 되었다. 이를 바탕으로 DI패턴을 적용한 Spring 프레임워크가 나오게 된 것이다. - 흥미롭군!!..

📌 초보 개발자에게 의존성 주입을 설명한다면

처음에 내가 작성한 "의존성 주입이란?" 에서 알아본 것 말고
개발 고수는 어떻게 초보자들에게 의존성 주입을 설명하는지도 한번 보자 ↓↓


의존성 주입(Dependency Injection)은 컴퓨터 과학에서 가장 헷갈리는 용어 중 하나다.
하지만 그 본질은 아주 간단하다:

어떤 클래스가 다른 클래스에 의존해야 할 때
그 의존 대상을 내부에서 직접 생성하지 말고
외부로부터 함수나 생성자의 매개변수로 받아서 사용하자.

✅ 예를 들어 설명하자면:

자동차(Car)는 연료(fuel)가 필요하지만,
자동차 자체가 연료를 생성하는 건 아니다.

현실에서는 연료는 외부 연료 공급원(예: 주유소, 연료통)으로부터
자동차로 주입되어 사용된다. 자동차가 연료를 만들지 않듯,
의존성도 외부에서 주입되어야 한다는 개념이다.

✅ 코드 예시

class GasolineSource {
  size_t amount;
  void transfer(size_t n, size_t& dest) {
    if (n <= amount) {
      amount -= n;
      dest += n;
    }
  }
};

class Car {
  size_t gastank;

  virtual void getGasoline(GasolineSource& gs, size_t amount) {
    if (gs.amount >= amount)
      gs.transfer(amount, gastank);
    else {
      // ERROR 처리
    }
  }
};

여기서 핵심은 Car 클래스는 GasolineSource의 내부 구현을 알 필요가 없다.
그저 transfer()라는 API만 알면 충분하다.

이렇게 외부에서 어떤 객체를 주입받고
그 객체의 구현이 아니라 인터페이스(API)만 사용하는 경우에는
언제든지 객체를 교체(스왑)할 수 있다.
또한, 객체를 직접 소유하지 않기 때문에 다른 코드에서도 자유롭게 재사용 가능하다.

✅ 의존성 주입은 다음과 같은 특징을 가진다:

  • 비소유(Non-owning) 형태의 컴포지션이다.

  • 특정 함수나 객체에서 임시로 참조하는 방식이다.

  • 객체는 제대로 생성되었고 정상 작동한다고 가정하고 사용할 수 있다.

  • 의존성이 적절히 관리되므로 객체가 잘못 생성된 경우라도
    전반적인 시스템이 망가지지 않도록 예외를 방지할 수 있다.

"이렇게 의존성과 객체 생성을 분리하면 객체 생성 과정에서 발생하는 문제로 인해
상위 객체의 생성까지 실패하게 되는 문제를 막을 수 있다."

✅ 그런데 왜 어렵게 느껴질까?

사실 이 개념은 매우 단순한데 학계나 아키텍처 문서에서는 너무 복잡하고 추상적인 용어로 설명해서 마치 "고급 개념"처럼 느껴지도록 만들곤 한다.

✅ DI가 복잡해지는 이유:

IoC와 DIP가 함께 등장하기 때문 의존성 주입(DI)은 종종 세 가지 개념과 함께 등장한다

  • Dependency Injection (DI) – 의존성은 외부에서 주입된다.

  • Inversion of Control (IoC) – 객체의 흐름을 자신이 직접 제어하지 않고, 외부에 맡긴다.

  • Dependency Inversion Principle (DIP) – 추상에 의존하고 구현에 의존하지 않는다.

✅ IoC vs DI 예시

IoC가 아닌 예 (잘못된 설계)

public class TextEditor {
  private SpellChecker spell_checker;

  public TextEditor() {
    spell_checker.init(default); // 직접 생성 및 초기화
  }
}

이 구조에서는 TextEditorSpellChecker를 직접 생성하고 관리하므로 SpellChecker가 바뀌면 TextEditor도 반드시 바꿔야 함.

IoC를 적용한 예 (좋은 설계):

public class TextEditor {
  private SpellChecker spell_checker;

  public TextEditor(SpellChecker checker) {
    this.spell_checker = checker;
  }
}

이 구조에서는 외부에서 이미 생성된 SpellChecker 인스턴스를 주입함.
→ 교체 가능성, 테스트 용이성, 재사용성이 모두 증가.

✅ DIP란?

DIP(의존성 역전 원칙)은 코드 기법이 아니라 설계 철학이다.

상위 모듈(비즈니스 로직)은 하위 구현(구체 클래스)에 의존하면 안 되고,
하위 구현이 상위 추상화에 맞춰야 한다.

즉, 인터페이스가 먼저고,
구체 클래스는 그 인터페이스에 맞춰서 구현돼야 한다.
그럼 언제든 구현체만 갈아끼우면 되니까!

✅ 마무리 정리!

  • 의존성 주입(DI)은 단순히 "필요한 객체는 외부에서 받아서 써라"는 개념.
  • 이걸 잘하면 → 유닛 테스트, 유지보수, 확장성이 훨씬 좋아짐.
  • DI는 객체를 직접 만들지 않음 → 대신 전달받아 사용함.
  • DI는 단독으로도 강력하지만, IoC와 DIP와 함께하면 더욱 설계가 강력해짐.
  • 실무에선 DI 컨테이너가 이 역할을 대신해서 객체들을 자동으로 관리해줌.
  • 결과적으로 → 코드는 유연하고 견고해지고, 테스트도 쉬워짐.

📌 장점

위에서도 계속 언급 했지만 정리해보자면

  1. 의존성이 줄어든다.
  2. 재사용성이 높은 코드가 딘다.
  3. 테스트하기 좋은 코드가 된다.
  4. 가독성이 높아진다.

이런 장점을 얻을 수 있는 패턴이다!

profile
Async FE 취업 준비중.. Await .. (취업완료 대기중) ..

0개의 댓글