💻 CS

12. DI와 DIP

elffffy 2026. 6. 26. 22:23
※ 이 글은 인프런 강의를 수강하며
개인 학습 목적으로 이해한 내용을 정리한 글입니다.

 

1️⃣ Dependency Injection(DI)?

메인 모듈이 직접 의존 객체를 생성하지 않고, 외부(injector)에서 주입받는 설계 패턴

Dependency Injection / https://miro.medium.com/1*Nd1aRAjjIfY8M0Xjb1xBVw.gif

 

외부에서 주입받는 구조로 만들면

모듈 간 결합도를 낮추고 구현체를 유연하게 교체할 수 있습니다.

 

코드로 바로 살펴봅시다!

 

우선, DI가 없는 버전입니다.

class BackendDeveloper:
    def coding(self):
        print("백엔드 개발자 : use Java")

class FrontendDeveloper:
    def coding(self):
        print("프론트엔드 개발자: use JavaScript")

class Project:
    def __init__(self):
        self.backend = BackendDeveloper()
        self.frontend = FrontendDeveloper() 

    def implement(self):
        self.backend.coding()
        self.frontend.coding()

project = Project()
project.implement()

 

ProjectBackendDeveloperFrontendDeveloper를 생성합니다.

 

하지만, 백엔드 개발자를 AI 개발자로 바꾸고 싶다면,

Project 내부 코드를 직접 수정해야 하죠🥲

 

또한, 클래스 간 결합이 강해 유지보수와 테스트가 어려워지기도 합니다...


그럼, 이제 DI를 적용한 버전을 한 번 볼까요?

class BackendDeveloper:
    def coding(self):
        print("백엔드 개발자 : use Java")

class FrontendDeveloper:
    def coding(self):
        print("프론트엔드 개발자 : use JavaScript")

class Project:
    def __init__(self, *developers): 
        self.developers = developers

    def implement(self):
        for dev in self.developers:
            dev.coding()

project = Project(BackendDeveloper(), FrontendDeveloper())
project.implement()

 

Project는 누가 들어올 지 전혀 모르는 구조입니다.

어떤 개발자든 외부에서 넣어주기만 하면, 실행할 뿐인데요!

 

예를 들어, AIDeveloper()를 추가하더라도

해당 클래스만 새로 만들면 될 뿐, 기존의 Project 코드는 수정하지 않아도 됩니다.

 

이처럼 의존성을 외부에서 주입하는 것이 DI의 핵심입니다!

 

DI의 단점으로는 복잡도가 증가하고 종속성 관련 에러를 추적하기 어려울 수 있습니다. 

하지만, 구현체 교체가 자유롭고, 단위 테스트와 마이그레이션이 쉬워지며,

코드 흐름을 추론하기도 훨씬 편해진다는 강력한 장점이 있습니다!


2️⃣ Dependency Inversion Principle(DIP) ?

🤷‍♀️ "그런데 잠깐,  Project가 여전히 하위 클래스에 의존하고 있는거 아닌가요?"

 

맞습니다!

예를 들어, coding 메서드가 deleting 메서드로 바뀐다면, 런타임에서 에러가 납니다.

이걸 설계 단계에서 막아주는 원칙이 바로 DIP입니다.

 

의존 관계 역전 원칙은 다음 두 가지의 규칙을 따릅니다.

  1. 상위 모듈은 하위 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다.
  2. 추상화는 세부 사항에 의존해서는 안 된다. 세부 사항이 추상화를 따라야 한다.

즉, "구체적인 클래스 말고, 인터페이스만 바라봐"라는 뜻인데요,

DIP가 적용된 버전을 보겠습니다!

from abc import ABC, abstractmethod

class Developer(ABC):      
    @abstractmethod
    def coding(self):
        pass

class BackendDeveloper(Developer):
    def coding(self):
        print("백엔드 개발자 : use Java")

class FrontendDeveloper(Developer):
    def coding(self):
        print("프론트엔드 개발자 : use JavaScript")

class Project:
    def __init__(self, *developers: Developer):
        self.developers = developers

    def implement(self):
        for dev in self.developers:
            dev.coding()

project = Project(BackendDeveloper(), FrontendDeveloper())
project.implement()

 

이제 Developer를 상속받지 않은 클래스는

coding()을 반드시 구현했다는 보장이 없으므로, 애초에 설계 단계에서 걸러낼 수 있습니다.

 

Project는 구체적인 클래스가 무엇인지 전혀 몰라도 되고,

Developer라는 약속만 지키면 누구든 주입할 수 있게 됩니다!


📝 마무리

오늘도 다소 어려운 개념을 공부한 것 같다.

코드와 함께 보니 확실히 이해는 좀 더 빠르게 된 것 같지만,,,

 

프로젝트 클래스 설계할 일이 있다면,

DI 구조와 DIP 원칙을 떠올릴 수 있으면 좋을 것 같다!