의존성 역전 원칙(Dependency Inversion Principle, DIP)
**의존성 역전 원칙(Dependency Inversion Principle, DIP)**은 SOLID 원칙 중 하나로, 객체 지향 설계에서 의존성의 방향을 역전시켜 상위 모듈이 하위 모듈에 의존하지 않도록 만드는 설계 원칙입니다. 이를 통해 모듈 간의 결합도를 낮추고, 더 유연하고 확장 가능한 코드를 작성할 수 있습니다.
의존성 역전 원칙이란?
정의
**고수준 모듈(High-level Module)**은 저수준 모듈(Low-level Module)에 의존해서는 안 된다.
두 모듈 모두 **추상화(Abstraction)**에 의존해야 한다.
**추상화(Abstraction)**는 세부 사항(Concrete Details)에 의존해서는 안 된다.
세부 사항이 추상화에 의존해야 한다.
쉽게 이해하기
일반적인 의존 관계는 아래와 같습니다:
High-level Module(고수준 모듈)
↓
Low-level Module(저수준 모듈)
의존성 역전을 적용하면 이렇게 됩니다:
High-level Module(고수준 모듈) ----> Interface(추상화)
Low-level Module(저수준 모듈) ---->
즉, 고수준 모듈과 저수준 모듈 모두 추상화에 의존하게 하여, 구현 세부 사항을 숨기고 모듈 간 결합도를 낮춥니다.
의존성 역전의 필요성
유연성 향상:
코드가 구체적인 구현에 의존하지 않기 때문에, 구현을 변경하거나 확장하기 쉬워집니다.
테스트 용이성:
추상화된 인터페이스를 사용하면 Mock 객체를 주입하여 단위 테스트를 쉽게 작성할 수 있습니다.
변경의 영향 최소화:
구현 세부 사항이 변경되어도 추상화 인터페이스만 유지되면 고수준 모듈에 영향을 주지 않습니다.
예시: 의존성 역전 원칙 적용 전
문제:
PaymentService
가 직접BankPaymentProcessor
라는 구체적인 클래스에 의존하고 있습니다.PaymentService
를 변경하려면BankPaymentProcessor
의 구현을 변경해야 합니다.
public class PaymentService {
private BankPaymentProcessor paymentProcessor;
public PaymentService() {
this.paymentProcessor = new BankPaymentProcessor();
}
public void processPayment() {
paymentProcessor.process();
}
}
public class BankPaymentProcessor {
public void process() {
System.out.println("Processing payment via bank");
}
}
예시: 의존성 역전 원칙 적용 후
해결:
PaymentService
는 이제PaymentProcessor
라는 추상화된 인터페이스에 의존합니다.BankPaymentProcessor
와 같은 구체적인 구현체는 인터페이스를 구현합니다.PaymentService
는BankPaymentProcessor
와 같은 구현체의 변경에 영향을 받지 않습니다.
사용
의존성 역전 원칙의 실제 활용
헥사고날 아키텍처:
고수준 모듈(애플리케이션 로직)은 추상화된 Port에 의존하고, 저수준 모듈(예: 데이터베이스, API)는 Port를 구현하는 Adapter로 동작.
DI(Dependency Injection):
Spring Framework와 같은 DI 컨테이너를 통해 의존성을 주입받아, 고수준 모듈과 저수준 모듈 간 결합도를 낮춤.
의존성 역전의 효과
결합도 감소: 코드가 구현 세부 사항에서 추상화로 의존성이 이동.
확장성 증가: 새로운 구현체를 추가해도 기존 코드를 수정하지 않아도 됨.
테스트 편의성: Mocking 등을 통해 독립적인 테스트 가능.
이 원칙은 특히 유지보수성과 확장성이 중요한 프로젝트에서 큰 가치를 발휘합니다. 😊