
'개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴'을 읽고 학습합니다. ( 최범균 저 )
SOLID의 마지막 원칙인 의존 역전 원칙은
' 고수준 모듈은 저수준 모듈의 구현에 의존하면 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다. ' 이다.
말이 어렵다..
먼저 고수준 모듈과 저수준 모듈이 무엇인지 정의부터 해보자.
고수준 모듈은 어떤 단일 기능을 제공하는 모듈이라고 정의할 수 있으며, 저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현으로 정의 가능하다.
아래의 예제를 보며 쉽게 이해해보자.

위의 예시는 바이트 데이터를 암호화하는 것이 이 프로그램의 의미 있는 단일 기능으로 고수준 모듈에 해당한다.
이 고수준 모듈은 데이터 읽기, 함호화, 데이터 쓰기라는 하위 기능으로 구성되어 있는데, 여기서 저수준 모듈이 이 하위 기능들을 실제로 어떻게 구현하는 것인지에 대한 책임을 다룬다.
고수준 모듈이 저수준 모듈에 의존할 때의 문제
고수준 모듈은 상대적으로 큰 틀에서 로직이 구성된다면, 저수준 모듈은 각 개별 요소(상세)의 구현에 대해 다룬다.
프로젝트의 요구 사항이 안정화되면 큰 틀에서 프로그램이 변경되기 보다는 상세 수준에서의 변경이 발생할 가능성이 높다.
예를 들어, 상품의 가격을 결정하는 정책을 고려해보면 상위 수준에서 다음과 같이 결정이 있을 수 있다.
쿠폰을 적용해서 가격 할인을 받을 수 있다.
쿠폰은 동시에 한 개만 적용 가능하다.
이는 고수준 모듈의 정책이다.
그리고 상세 내용( 저수준 모듈 )을 살펴보면, 일정 금액 할인 쿠폰이나 비율 할인 쿠폰 등의 다양한 쿠폰이 존재할 수 있다.
상위 수준( 고수준 모듈 )에서의 쿠폰 정책은 한 번 안정화되면 쉽게 변하지 않지만, 쿠폰 그 자체의 기능은 요구에 따라 다양한 종류( 구현체 )를 사용할 수 있다.
여기서 저수준 모듈인 쿠폰 기능을 사용하는 고수준 모듈( 가격계산 모듈 )이 개별적 쿠폰 기능 구현체에 의존하게 되면 쿠폰 기능 요구 사항의 변경에 따라 가격 계산 모듈도 변경되는 상황이 유발된다.
고수준 모듈을 클라이언트로 바라보면, 필요한 기능의 변경에 있어 닫혀있지 않다. 따라서 객체 지향적 설계를 위반하며 프로그램 변경과 확장을 어렵게 만든다.
객체 지향적 설계에 근거하여 저수준 모듈이 변경되어도 고수준 모듈( 클라이언트 )은 변경되지 않는 것을 지향하며, 이를 위한 것이 의존 역전 원칙이다.
의존 역전 원칙을 통한 변경의 유연성 확보
의존 역전 원칙은 위와 같은 문제를 저수준 모듈이 고수준 모듈을 의존하도록 만들어 해결한다.
그런데, 분명 고수준 모듈이 저수준 모듈의 기능이 필요하고, 이를 사용한다는 것은 고수준 모듈이 저수준 모듈에 의존한다는 의미인데 어떻게 저수준 모듈이 고수준 모듈을 의존하도록 만드는 것일까??
답은 추상화에 있다.
이전에 파일 암호화 예제에서 이 방법을 사용하여 이미 의존 역전 원칙을 지킨 적이 있다.

위의 그림처럼 기존에는 FileDataReader라는 저수준 모듈에 FlowController인 고수준 모듈이 의존하는 형태였다.
그러나, ByteSource 추상 타입을 도출하여 요구사항의 변경에 따른 유연함을 확보하였다.
즉, 고수준 모듈인 FlowController과 저수준 모듈인 FileDataReader 모두 추상화 타입인 ByteSource 에 의존하므로, 고수준 모듈의 변경 없이 저수준 모듈을 변경이 가능해진 것이다.
또한 앞서 다뤘던 리스코프 치환 원칙이과 개방 폐쇄 원칙을 따르는 설계의 기반으로도 볼 수 있다.( 모든 원칙들이 객체 지향이라는 공통된 목적을 두고 논리적 근거과 원리를 구성하는 것이 느껴진다. )
소스 코드 의존과 런타임 의존
의존 역전 원칙은 ' 소스 코드 '에서 의존을 역전시키는 원칙이다.
의존 역전 원칙을 적용하기 전의 코드를 보면, FlowController 클래스의 코드는 FileDataReader 를 의존한다.
public class FlowController {
public void process() {
// 소스 코드에서 FileDataReader에 대한 의존 발생
FileDataReader reader = new FileDataReader();
...
}
}
이 코드에 의존 역전 원칙을 적용하면 FileDataReader 클래스는 ByteSource에 의존하게 된다.
// 상세 구현에서 추상 타입에 의존
public class FileDataReader implements ByteSource {
...
}
여기서 ByteSource 인터페이스는 고수준 모듈인 FlowController 입장에서 필요에 만들어진다.
ByteSource 타입은 FlowController에 의하여 만들어지고 사용되는 인터페이스인데, 해당 인터페이스에 FileDataReader이 의존한다.
그럼 기존에 고수준 모듈인 FlowController 이 저수준 모듈 FileDataReader에 의존했던 상황에서, 이제는 FlowController 에 의해 만들어진 ByteSource에 FileDataReader가 의존하는 상황으로 변화하였다.
이런 의미에서 의존이 역전한 상태를 근거로 원칙의 이름이 의존 역전 원칙인 것이다.
한편, 이 원칙은 소스 코드 상에서 의존의 역전을 의미하는 것이지 런타임에서의 의존은 기존과 똑같이 고수준 모듈이 저수준 모듈에 의존한다.
중간에 추상화 객체를 통해 소스 코드 상에서는 의존이 역전된 것이지만, 당연하게도 고수준 모듈이 저수준 모듈의 기능을 필요로 하는 것은 변함없기에 런타임에서의 의존은 고수준 모듈이 저수준 모듈에 의존하게 된다.
단순하게 변경의 유연함을 얻기 위한 방향성이지 실제 기능의 변경과는 상관이 없는 것이다.

의존 역전 원칙과 패키지
의존 역전 원칙은 패키지 구성 측면에서도 개방 폐쇄 원칙을 준수할 수 있게 만든다.
의존 역전 원칙을 적용하기 전, 데이터 읽기 타입은 FileDataReader를 소유한 패키지가 소유하였다.

그런데 의존 역전 원칙을 적용하며 아래와 같이 데이터 읽기 기능을 위한 타입을 고수준 모듈이 소유할 수 있다.

타입의 소유 역전은 각 패키지를 독립적으로 배포할 수 있도록 만들어 준다. (독립적으로 배포한다는 건 jar 파일이나 DDL 등의 파일로 배포한다는 것을 뜻한다.)
예를 들어, 소켓으로 데이터를 읽는 추가 요구 사항이 있다고 하자.
그럼 지금과 같은 구조에서는 아래의 그림처럼 별도의 jar 파일로 만들어 배포하고 filedata.jar 파일을 socketdata.jar 파일로 교체하여 데이터를 소켓으로 읽어 오도록 변경 가능하다.

만약, 여기서 추상화를 하였지만, 데이터 읽기 타입이 filedata 패키지에 그대로 있었다면??
그럼 아래와 같은 구조를 가지게 될 것이다. 그리고 추가 요구 사항인 소켓으로 데이터 읽기를 구현한다면 socketdata.jar뿐만 아니라 필요없는 filedata.jar도 필요하게 될 것이다.

따라서 의존 역전 원칙을 준수하며 패키지 구조도 이에 맞춰 구성한다면, 패키지 수준까지도 개방 폐쇄 원칙을 확장시킬 수 있는 것이다.
'Java > 객체지향' 카테고리의 다른 글
| 객체 지향과 디자인 패턴 Chapter 05 - 인터페이스 분리 원칙(SOLID) (1) | 2024.11.14 |
|---|---|
| 객체 지향과 디자인 패턴 Chapter 05 - 리스코프 치환 원칙(SOLID) (5) | 2024.11.13 |
| 객체 지향과 디자인 패턴 Chapter 05 - 개방 폐쇄 원칙(SOLID) (1) | 2024.11.05 |
| 객체 지향과 디자인 패턴 Chapter 05 - 설계원칙: 단일 책임 원칙(SOLID) (1) | 2024.11.02 |
| 객체 지향과 디자인 패턴 Chapter 04 - 재사용: 상속보단 조립 (0) | 2024.10.30 |
댓글