Software Design

DI·IoC·DIP 차이, 세 용어 한 번에 완벽 정리

DI·IoC·DIP는 층위가 다른 개념입니다. DIP는 무엇에 의존할지 정하는 원칙, IoC는 제어권을 누가 갖는지의 방향, DI는 의존성을 넣어주는 기법이라는 구분을 예제와 면접 답변까지 정리했습니다.

이석우iOS Developer6분 읽기
DI·IoC·DIP 차이, 세 용어 한 번에 완벽 정리 대표 이미지

개발 공부하다 보면 DI, IoC, DIP 이 세 용어 때문에 한 번쯤 머리가 아팠던 경험 다들 있으실 거예요.

강의나 글에서 “IoC 컨테이너가 DI로 의존성을 주입해서 DIP를 지킨다” 이런 식으로 세 개를 한 문장에 몰아서 말하니까, 각각이 뭔지 헷갈리기 시작하죠.

결론부터 말씀드릴게요. 세 용어는 층위가 다릅니다. DIP는 원칙(왜 그래야 하는지), IoC는 그 원칙을 실현하는 큰 방향(누가 제어권을 갖는지), DI는 그 방향을 구현하는 구체적 기법(어떻게 넣어주는지)이에요.

이 글 하나 읽으시면 세 개가 어떻게 연결되는지, 면접에서 물어봐도 막힘없이 대답할 수 있을 정도로 정리되실 거예요. 코드 예시도 최대한 짧게 넣어봤어요.

DIP·IoC·DI를 WHAT·WHO·HOW에 대응시킨 3분할 비교 썸네일
DI·IoC·DIP 차이, 세 단어만 구분해도 절반은 정리됩니다

세 용어의 한 줄 정리부터

먼저 딱 한 줄씩만 외우고 시작하면 훨씬 편해요.

  • DIP (Dependency Inversion Principle, 의존 역전 원칙): 구체적인 것 말고 추상(프로토콜)에 의존하라는 설계 원칙
  • IoC (Inversion of Control, 제어의 역전): 프로그램의 제어 흐름을 내 코드가 아니라 프레임워크/컨테이너가 갖는 것
  • DI (Dependency Injection, 의존성 주입): 필요한 객체를 내부에서 만들지 않고 외부에서 넣어주는 기법

이 셋의 관계는 이렇게 돼요.

DIP가 목표이자 원칙이라면, IoC는 그 목표를 이루는 넓은 개념이에요. DI는 IoC를 실제로 구현하는 구체적인 방법 중 하나고요.

한마디로 추상화 수준이 DIP > IoC > DI 순서로 내려온다고 보시면 됩니다.

DIP에서 IoC를 거쳐 DI·콜백·템플릿 메서드로 갈라지는 계층 다이어그램
원칙에서 기법까지, 세 용어가 놓인 층위

DIP는 “무엇에 의존할까”의 문제

DIP는 SOLID(객체지향 5대 설계 원칙) 중 D에 해당하는 의존 역전 원칙이에요.

핵심은 두 가지예요. 상위 모듈이 하위 모듈에 의존하면 안 되고, 둘 다 추상에 의존해야 한다는 것.

말이 어렵죠. 코드로 보면 훨씬 쉬워요.

주문 서비스가 특정 결제사(카카오페이)를 직접 붙들고 있는 나쁜 예를 볼게요.

// 나쁜 예: 구체 클래스에 직접 의존
class OrderService {
    private let pay = KakaoPay() // 결제사 바꾸려면 여기를 뜯어야 함
}

이러면 나중에 네이버페이로 바꿀 때 OrderService 코드를 직접 고쳐야 해요. 결제 수단이 늘 때마다 상위 모듈이 흔들리는 거죠.

DIP는 이걸 뒤집어요. 결제라는 추상(프로토콜)을 두고 거기에만 의존하게 만드는 거예요.

protocol PayGateway { func pay(amount: Int) }

class OrderService {
    private let pay: PayGateway // 구체 클래스가 아니라 추상에 의존
    init(pay: PayGateway) { self.pay = pay }
}

이제 카카오페이든 네이버페이든 PayGateway를 구현하기만 하면 OrderService는 건드릴 필요가 없어요. 이게 “의존 방향이 역전됐다”는 말의 의미예요.


IoC는 “누가 제어권을 갖느냐”의 문제

IoC, 제어의 역전은 조금 더 넓은 개념이에요.

보통 우리가 짜는 코드는 내가 흐름을 다 통제하죠. 내가 객체를 만들고 메서드를 호출하고 순서를 정하고요.

IoC는 이 제어권을 뒤집어요. 내가 부르는 게 아니라, 프레임워크가 나를 부르는 구조로요.

흔히 “할리우드 원칙”이라고도 불러요. “먼저 연락하지 마세요, 저희가 연락드릴게요(Don’t call us, we’ll call you).”

Swinject 같은 DI 컨테이너를 예로 들면, 내가 객체를 직접 만드는 게 아니라 컨테이너가 대신 객체를 만들고 관리하고 연결해줘요. 제어권이 나에서 컨테이너로 넘어간 거예요.

여기서 중요한 포인트 하나. IoC는 DI만 있는 게 아니에요.

  • 프레임워크가 콜백을 호출하는 것
  • 템플릿 메서드 패턴
  • UIKit이 viewDidLoad 같은 생명주기 메서드를 대신 불러주는 것

이런 것도 전부 IoC의 사례예요. DI는 그중에서 “의존성을 넣어주는” 방식에 특화된 하위 개념일 뿐이에요.

Don't call us, we'll call you 카드가 놓인 책상 위 구형 전화기
제어권이 넘어간다는 것, IoC는 이 한 줄이 전부예요

DI는 “어떻게 넣어주느냐”의 문제

DI, 의존성 주입은 IoC를 구현하는 가장 대표적인 기법이에요.

아까 DIP 예제에서 OrderService가 PayGateway를 생성자로 받았죠? 그게 바로 DI예요. 필요한 객체를 내부에서 직접 만들지 않고 외부에서 넣어주는 것.

주입 방식은 크게 세 가지예요.

주입 방식 설명 추천도
생성자 주입 init으로 의존성을 받음 가장 권장 (불변, 필수 의존성 명확)
메서드 주입 메서드 파라미터로 받음 선택적 의존성일 때
프로퍼티 주입 생성 후 프로퍼티에 대입 옵셔널·var가 되어 비권장

Swift에서는 생성자(이니셜라이저) 주입을 권장해요. 의존성이 let으로 고정돼서 안전하고, 테스트할 때 가짜 객체(Mock)를 넣기도 쉽거든요.

정리하면 이렇게 연결돼요.

DIP라는 원칙을 지키기 위해, IoC라는 방향으로 제어권을 넘기고, 그 구체적 수단으로 DI를 쓴다.

이 한 문장이 세 용어의 관계를 가장 압축한 표현이에요.

화이트보드에 WHY·WHO·HOW 세 상자를 화살표로 연결한 정리 도식
왜·누가·어떻게 이 세 질문으로 나눠 적어두면 안 헷갈려요

자주 나오는 질문 정리

Q. DI랑 IoC는 같은 말 아닌가요?

아니에요. IoC가 더 넓은 개념이고, DI는 IoC를 구현하는 여러 방법 중 하나예요. 모든 DI는 IoC지만, 모든 IoC가 DI는 아니에요.

Q. DIP를 지키면 자동으로 DI를 쓰는 건가요?

꼭 그렇진 않아요. DIP는 “추상에 의존하라”는 원칙이고, 그 추상 구현체를 외부에서 넣어주는 순간 DI가 되는 거예요. 원칙과 기법은 별개예요.

Q. Swinject 같은 라이브러리 없이도 DI를 쓸 수 있나요?

네. 위 Swift 예제처럼 생성자로 직접 넣어주는 것도 DI예요. Swinject나 Factory는 이걸 자동화해주는 도구일 뿐, DI 자체는 라이브러리가 없어도 성립해요.


정리해볼게요. DIP는 왜(원칙), IoC는 누가(제어권), DI는 어떻게(기법). 이 세 질문으로 나눠서 기억하시면 다시는 안 헷갈리실 거예요.

층위가 다르다는 걸 알고 나면 DI 관련 코드가 완전히 다르게 보이실 거예요. 여러분도 오늘 이 글로 개운하게 정리되셨으면 좋겠어요. 화이팅이에요!

이어서 읽기