티스토리 뷰
목차
1. DIP란?
DIP (Dependency Inversion Principle)
"상위 모듈은 하위 모듈에 의존하면 안 된다"
모두가 추상화(인터페이스)에 의존해야 한다"
📌 DIP의 핵심
- 고수준 모듈(비즈니스 로직)과 저수준 모듈(구현 세부사항)이 서로 구체적인 것에 의존하지 않고, 공통된 추상화에 의존해야 한다는 원칙이다.
2. DIP가 필요한 이유
❌ DIP를 지키지 않으면
- 고수준 로직이 저수준 세부 구현에 강하게 결합
- 구현이 바뀌면 고수준 로직도 수정해야 해서 유지보수성 급격히 저하
- 코드 재사용과 확장이 어려움
✅ DIP를 지키면
- 고수준 모듈이 구현 변화에 영향받지 않음
- 새로운 기능 추가/변경 시 기존 로직에 영향 최소화
- 의존성 관리가 명확해지고 테스트가 쉬워짐
❌3. DIP 위반 예시
public class FileLogger {
public void write(String message) {
// 파일에 로그 쓰기
}
}
public class OrderService {
private final FileLogger logger = new FileLogger(); // 직접 생성 및 의존
public void createOrder() {
logger.write("주문 생성됨");
}
}
📉 문제점
- OrderService가 FileLogger 구체 클래스에 직접 의존
- FileLogger를 변경하거나 다른 Logger로 바꾸려면 OrderService를 수정해야 함 -> OCP도 위반
- 테스트 시 Mock 주입이 불가능
✅4. DIP를 만족시키는 설계
📌 해결 방법 : 추상화 도입 + 의존성 주입
public interface Logger {
void write(String message);
}
public class FileLogger implements Logger {
public void write(String message) {
// 파일에 로그 쓰기
}
}
public class ConsoleLogger implements Logger {
public void write(String message) {
// 콘솔에 출력
}
}
public class OrderService {
private final Logger logger;
public OrderService(Logger logger) { // 생성자 주입
this.logger = logger;
}
public void createOrder() {
logger.write("주문 생성됨");
}
}
[OrderService] ---> [Logger 인터페이스] <--- [FileLogger]
<--- [ConsoleLogger]
<--- [MockLogger]
- 이제 OrderService는 Logger라는 추상화에만 의존
- FileLogger, ConsoleLogger 구현체가 바뀌어도 OrderService는 변경 없음 -> DIP 만족 ✅
- 테스트 시 MockLogger로 교체 가능
5. 실무에서 자주 보는 DIP 위반 사례
| 위반 사례 | 문제점 |
| Controller에서 직접 Repository 생성 (new) | 테스트/확장 어려움 |
| Service가 직접 외부 API Client를 생성 | 구현 변경이 어렵고 테스트 불가능 |
| Manager 클래스가 특정 구현체에 직접 의존 | OCP, DIP 모두 위반 |
6. DIP를 지키는 방법
✔️ 인터페이스 기반 설계
- 항상 역할(추상화)에 의존하고, 구현체는 DI(Dependency Injection)로 주입
✔️ 의존성 주입(DI) 활용
- 생성자 주입
- Setter 주입
- 인터페이스 주입 등
📌 Spring Framework는 기본적으로 DI 기반으로 동작 -> DIP를 자연스럽게 지킴
@Service
public class OrderService {
private final PaymentGateway gateway;
@Autowired
public OrderService(PaymentGateway gateway) {
this.gateway = gateway;
}
}
7. Spring과 DIP
| 구조 | 설명 |
| 의존성 주입 (DI Container) | Bean 등록 및 주입으로 DIP 실현 |
| 인터페이스 + 구현체 분리 | Service, Repository 등 모두 인터페이스로 추상화 |
| Profile, Qualifier 사용 | 상황에 따라 구현체 다르게 주입 |
- Spring에서는 DIP를 지키는 것이 "선택"이 아니라 "기본"이다.
8. Spring에서 DIP가 실현되는 구조 흐름도
✅ Spring Framework의 구조 요약
[Controller / Service / Repository]
|
| (DI - 의존성 주입)
v
[인터페이스(역할)] <--- 여러 구현체(구현)
- 예시 흐름
- 개발자는 인터페이스만 설계
- 구현체는 @Component, @Service, @Repository 등으로 등록
- Spring Container가 주입 시 자동으로 인터페이스에 맞는 구현체를 DI
- 실행 시점에 어떤 구현체를 주입할지는 외부 설정 or @Qulifier 사용
🎯 정리 요약
| 항목 | DIP 미적용 | DIP 적용 + Spring |
| 의존성 | 구현체 직접 의존 | 인터페이스에 의존 |
| 변경 유연성 | 낮음 (수정 필수) | 높음 (DI로 주입) |
| 테스트 용이성 | 어려움 | Mock 주입 쉬움 |
| 설계 유연성 | 구현 변경 시 영향 큼 | 구조 변경에 강함 |
| Spring 적용 | 불가 | DI 컨테이너가 자동 관리 |
✅ 마무리 요약
- DIP는 "상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다"는 원칙
- DIP를 지키지 않으면 구현 변경에 고수준 로직이 휘둘리게 됨
- 인터페이스 기반 설계 -> 의존성 주입(DI)으로 DIP를 실현할 수 있다.
- Spring은 기본적으로 DIP 철학을 실현하는 프레임워크이다.
🔜 다음 포스팅에서는 SOLID 정리편 - SOLID 5대 원칙 통합 요약 & 실무 적용 사례 정리라는 주제를 다뤄보려고 한다.
'IT > SOLID' 카테고리의 다른 글
| 💎SOLID 원칙 완전 정복 - 객체지향 설계의 뼈대를 만드는 5가지 철칙 (0) | 2025.05.07 |
|---|---|
| ISP - 인터페이스 분리 원칙(Interface Segregation Principle) (0) | 2025.04.28 |
| LSP - 리스코프 치환 원칙 (Liskov Substitution Principle) : "부모를 대체해도 문제가 없어야 한다" (0) | 2025.04.25 |
| OCP - 개방/폐쇄 원칙 (Open/Closed Principle) (0) | 2025.04.24 |
| SRP - 단일 책임 원칙 (Single Responsibility Principle) (0) | 2025.04.21 |