의존 역전 원칙(Dependency Inversion Principle)
Last updated
Last updated
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
고수준 모듈과 저수준 모듈을 정의하면 아래와 같다.
고수준 모듈: 어떤 의미 있는 단일 기능을 제공하는 모듈
저수준 모듈: 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현
암호와 예제를 다시 보면, 고수준 모듈과 저수준 모듈은 아래와 같이 구분할 수 있다.
바이트 데이터를 암호화한다는 것은 프로그램의 의미 있는 단일 기능으로서 고수준 모듈에 해당한다. 고수준 모듈은 데이터 읽기, 암호화, 데이터 쓰기라는 하위 기능으로 구성되는데, 저수준 모듈은 이 하위 기능을 실제로 어떻게 구현할 지에 대한 내용을 다룬다.
고수준 모듈은 상대적으로 큰 틀(상위 수준)에서 프로그램을 다룬다면, 저수준 모듈은 각 개별 요소(상세)가 어떻게 구현될지에 대해 다룬다.
예를 들어, 상품의 가격을 결정하는 정책을 생각해보면 상위 수준에서 다음과 같은 결정이 내려질 수 있을 것이다.
쿠폰을 적용해서 가격 할인을 받을 수 있다.
쿠폰은 동시에 한 개만 적용 가능하다.
이는 고수준 모듈의 정책이다. 상세 내용으로 들어가 보면 일정 금액 할인 쿠폰에서 비율 할인 쿠폰등 다양한 쿠폰이 존재할 수 있다.
여기서, 쿠폰을 이용한 가격 계산 모듈(상위 수준)이 개별적인 쿠폰(하위 수준)에 의존하게 되면 어떤 일이 벌어질까?
이러한 경우, 새로운 쿠폰 구현이 추가되거나 변경될 때마다 가격 계산 모듈이 변경되는 상황이 발생할 것이다.
위와 같은 경우, 프로그램의 변경을 어렵게 만든다. 우리가 원하는 것은 저수준 모듈이 변경되더라도 고수준 모듈은 변경되지 않는 것인데, 이를 위한 원칙이 바로 의존 역전 원칙이다.
의존 역전 원칙은 추상화를 이용해서 저수준 모듈이 고수준 모듈을 의존하게 만든다.
앞서 파일 암호화 예에서 이미 봤듯이, 최초에 FlowController 객체는 FileDataReader 객체에 직접적으로 의존하고 있었으나, ByteSource 추상 타입을 도출함으로써 FlowControlelr와 FileDataReader가 모두 추상 타입인 ByteSource에 의존하도록 만들었다.
고수준 모듈인 FlowController와 저수준 모듈인 FileDataReader가 모두 추상화 타입인 ByteSource에 의존함으로써, 고수준 모듈의 변경 없이 저수준 모듈을 변경할 수 있는 유연함을 얻게 되었다.
즉, 의존 역전 원칙은 리스코프 치환 원칙과 개방 폐쇄 원칙의 기반이 되는 원칙이다.
의존 역전 원칙은 소스 코드에서의 의존을 역전시키는 원칙이다.
의존 역전 원칙을 적용하기 전에 FlowController의 소스 코드는 FileDataReader를 의존하고 있었다.
이 코드에 의존 역전 원칙을 적용함으로써 오히려 FileDataReader의 소스 코드가 추상화 타입인 ByteSource에 의존하게 되었다.
ByteSource 인터페이스는 저수준 모듈보다는 고수준 모듈인 FlowController 입장에서 만들어지는데, 이것은 고수준 모듈이 저수준 모듈에 의존했던 상황이 역전되어 저수준 모듈이 고수준 모듈에 의존하게 된다는 것을 의미한다.
소스 코드 상에서의 의존은 역전되었지만, 런타임에서의 의존은 아래와 같이 고수준 모듈의 객체에서 저수준 모듈의 객체로 향한다.
의존 역전 원칙은 런타임의 의존이 아닌 소스 코드의 의존을 역전시킴으로써 변경의 유연함을 확보할 수 있도록 만들어 주는 원칙이지, 런타임에서의 의존을 역전시키는 것은 아니다.
의존 역전 원칙은 타입의 소유도 역전시킨다.
의존 역전 원칙을 적용하기 전, 데이터 읽기 타입은 FileDataReader를 소유한 패키지가 소유하고 있었다.
그런데, 의존 역전 원칙을 적용함으로써 아래와 같이 데이터 읽기 기능을 위한 타입을 고수준 모듈이 소유하게 된다.
타입의 소유 역전은 각 패키지를 독립적으로 배포할 수 있도록 만들어 준다. (독립적으로 배포한다는 건 jar 파일이나 DDL 등의 파일로 배포한다는 것을 뜻한다.)
예를 들어, 파일이 아닌 소켓으로부터 데이터를 읽어 오는 기능으로 변경해야 한다고 하자.
이 경우 배포 기준이 되는 패키지는 아래와 같이 별도의 jar 파일로 만들어질 수 있을 것이며, 기존의 filedata.jar 파일을 socketdata.jar 파일로 교체함으로써 데이터를 파일에서 소켓으로부터 읽어 오도록 변경하 수 있게 된다.
만약 타입의 소유가 고수준 모듈로 이동하지 않고 filedata 패키지에 그대로 있었다면 어떻게 될까?
이 경우 패키지 구조는 아래와 같이 구성이 되고, 기존 파일 구현 대신 소켓 구현을 사용하게 되면 socketdata.jar 뿐만 아니라 기능상 필요없는 filedata.jar도 필요하게 된다.
따라서 의존 역전 원칙은 개방 폐쇄 원칙을 클래스뿐만 아니라 패키지 수준까지 확장시켜 주는 디딤돌이 된다.