의존성 역전 원칙(DIP)란?
의존성 역전 원칙
의존성 역전 원칙(Dependency Inversion Principle, DIP)은 소프트웨어 설계의 SOLID 원칙 중 하나로, 구현 세부 사항이 아닌 추상화에 기반을 둔다. 즉, 고수준 모듈(High-level modules)이 저수준 모듈(Low-level modules)에 의존하지 않도록 하는 설계 원칙인데, 왜냐하면 고수준 모듈과 저수준 모듈 모두 추상화에 의존해야 하며, 추상화는 세부 사항에 의존해서는 안되기 때문이다.
이 때, 저수준 모듈은 메인클래스나 객체를 말하고 고수준 모듈은 인터페이스나 추상 클래스를 의미한다. 고수준 모듈이 상대적으로 큰 틀에서 어떤 의미있는 단일 기능을 제공하는 모듈이라면, 저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 각 개별 요소가 어떻게 구현될지를 다룬다. 사실 직접적으로 인터페이스나 추상 클래스를 구현하는 것은 저수준 모듈이기 때문에 고수준 모듈이 저수준 모듈에 의존한다고 할 수 있는데, 저수준 모듈은 빈번하게 변경되고 고수준 모듈은 그에 따른 영향을 받으면 안되기 때문에 고수준과 저수준의 의존관계를 역전시켜야 한다는 것이다. DIP는 소프트웨어의 유지보수성과 확장성을 향상시키는 데 중요한 역할을 하며, 다양한 구성 요소 간 결합도를 낮추는 데 도움을 준다. 이는 시스템의 유연성을 높이고, 변경 사항이 시스템의 다른 부분에 미치는 영향을 최소화한다.
예시
매개변수로 객체를 받을 때 구체 클래스(Concrete class) 타입으로 받는 것이 아니라, 다형성을 이용해 인터페이스 타입으로 통신하는 것이 좋다.
아래 코드의 예시에서 CarRider
은 Car
인터페이스에 의존하며, 이는 Volvo
에 의해 구현된다. 이러한 구조는 CarRider
가 자동차 구동 방식의 세부 사항에 대해 알 필요가 없게 하며, 이는 유지보수성과 확장성을 향상시킨다. 왜냐하면 CarRider
는 자동차 구동 방식이 변경되더라도 수정할 필요가 없으며, 새로운 운행 방식을 쉽게 추가할 수 있기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Car{} //인터페이스
class Volvo implements Car{
public void start() {
//자동차 구동 방식 구현
}
}
// 클라이언트
class CarRider {
Car car;
public CarRider(Car car){
this.car = car;
}
void perform() {
car.start();
}
}
실제 자바에서의 의존성 역전 원칙의 예시를 들어보면 대표적으로 컬렉션 프레임워크가 있다. 일반적으로 ArrayList
나 HashSet
자료형을 인스턴스화 할 때 변수 타입을 ArrayList
나 HashSet
같은 구체 클래스 타입으로 선언하는 것이 아니라, List
나 Set
같은 인터페이스 타입으로 선언하는 것을 봤을 것이다. 이것이 바로 DIP 원칙을 따른 코드 선언이다.
상위 클래스일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않을 가능성이 높기 때문에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 것이다.
1
2
3
4
// 변수 타입을 고수준의 모듈인 인터페이스 타입으로 선언하여 저수준의 모듈을 할당
List<String> myList = new ArrayList()<>;
Set<String> mySet = new HashSet()<>;
Map<int, String> myMap = new HashMap()<>;
장점
- 시스템의 다양한 구성 요소 간 결합도를 낮춰 각 부분을 독립적으로 개발하고 테스트할 수 있게 함
- 시스템의 각 부분을 독립적으로 검증할 수 있게 하여 테스트 용이성 높임
- 유지보수성, 확장성, 유연성 향상