고수준 모듈(업무 규칙)이 저수준 모듈(데이터베이스(DB), HTTP(Hypertext Transfer Protocol) 클라이언트 같은 세부 구현)에 직접 의존하지 않고, 둘 다 추상화에 의존해야 한다는 원칙. 의존 방향이 "업무 → 세부"에서 "세부 → 추상화 ← 업무"로 뒤집힌다.
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def save(self, data): ...
class UserService:
def __init__(self, db: Database): # 구체 클래스가 아니라 추상화에 의존
self.db = db- DB를 바꾸거나 테스트용 가짜를 넣을 때
UserService를 고치지 않아도 된다 - 추상화는 고수준 쪽 필요에 맞춰 정의한다. 저수준 라이브러리의 API(Application Programming Interface)를 그대로 베낀 인터페이스는 역전이 아니다
- 파이썬은 ABC(Abstract Base Class)나 프로토콜(Protocol), TypeScript는 인터페이스와 구조적 타이핑으로 표현한다
원칙과 기법
DIP(Dependency Inversion Principle)는 "무엇에 의존해야 하는가"라는 원칙이고, 의존성 주입(Dependency Injection)은 그 구현을 바깥에서 넣어 주는 기법이다. 주입 없이도 DIP를 지킬 수 있고, 주입을 써도 구체 클래스를 주입하면 DIP는 아니다.
React에서
- Context로 주입: 컴포넌트가
axios를 직접 import하지 않고HttpClient같은 추상화를 Context에서 받는다. 테스트에서는 가짜 클라이언트를 Provider로 넣는다(MSW로 API 모킹도 다른 방식의 대안) - Props·children 합성: 버튼이 특정 아이콘 컴포넌트를 import하는 대신
iconprop으로 받는다(컴파운드 컴포넌트 패턴)
SOLID 원칙의 D이고, React로 보는 SOLID에 React 쪽 예가 더 있다. 과하면 구현이 하나뿐인 인터페이스만 늘어나니 교체나 테스트 필요가 실제로 있을 때 쓴다(YAGNI).