본문 바로가기

IT/개발지식

객체지향 의존 역전 원리(DIP) 제대로 알기

반응형

스프링+자바 코드를 보다 보면 흔하게 Service 인터페이스를 구현하고 있는 ServiceImpl 클래스 볼 수 있다. 물론 나도 컴퓨터공학 전공자로서 대학시절에 객체지향의 기본 원리에 대해서는 공부를 했지만 정작 실무를 접한 주니어 때는 왜 꼭 인터페이스-구현체 구조를 만들어야 되는지 제대로 이해하지 못했다.

 

물론, 인터페이스가 무엇인지는 알고 있었다.

 

(그림1)

 

그러니까 Dog도 Animal이고 Cat도 Animal이니까 결국 Animal로 묶어서 추상화할 수 있고 인터페이스로 공통된 규약을 정의할 수 있다는 개념이 아닌가.

 

하지만 인터페이스와 구현체가 1:1인 경우 딱히 다형성의 이점을 활용하는 것도 아니다. 이런 경우 꼭 인터페이스를 사용할 필요가 있겠냐는 생각이 들었고 실제 그렇게(인터페이스가 필요 없다) 설명하고 있는 블로그들도 있다. 하지만 의존 역전 원리(Dependency Inversion Principle)를 잘 이해하면 그렇지 않다는 것을 알 수 있다.

 

인터페이스는 구현체를 모른다

인터페이스는 구현부가 없다. 구현체가 어떻게 구현되어 있는지 모르고, 알 필요도 없다. 하지만 구현체는 인터페이스를 구현해야하므로, 인터페이스에 대해서 잘 알고 있다. 아래 그림을 보자.

 

(그림2) 출처 - Robert C. Martin - Clean Architecture

 

ServiceImpl은 Service에 대해 Compile 의존성을 가진다. (import 하므로) 하지만 Service는 반대로 ServiceImpl에 대해 Compile 의존성을 가지지 않는다. 구현체가 어떻게 변경되어도 인터페이스는 그것을 신경쓰지 않아도 된다. 프로그램의 제어 흐름(런타임에는 프로그램의 흐름이 어떻게 되겠는가?)은 왼쪽에서 오른쪽으로 흐르는데 반해 Compile 의존성의 방향은 반대가 되기 때문에 의존성이 역전됐다고 하여 DIP라고 하는 것이다.

 

그래서 DIP가 어쨌다는 걸까? 인터페이스가 변경 가능성이 높은 구현체를 신경쓰지 않아도 되므로, 경계를 기준으로 왼쪽과 오른쪽은 독립적인 개발이 가능하다. 인터페이스만 잘 정의해둔다면 서로 다른 개발자가 동시에 개발을 진행할 수 있는 것이다. 뿐만 아니라 구현체와 상관없이 Service를 Mocking하여 단위 테스트를 하기도 용이하다.

 

만약 인터페이스가 Database Repository 였다면? 구현체에서 ORM을 쓸수도, Mapper를 쓸 수도 있다. 하지만 인터페이스는 이러한 상세 구현의 변경 사항에 대해 보호를 받을 수 있다. 의존 관계가 역전됨으로써 소프트웨어에 경계가 그어지는 것이다.

 

로버트 마틴의 Clean Arichitecture 18장을 보면 그림 2에서 경계를 기준으로 왼쪽을 고수준(High level) 컴포넌트, 오른쪽을 저수준(Low level) 세부구현이라고 표현한다. 즉, 고수준 컴포넌트는 잦은 변경에 노출되어 있는 저수준 세부사항에 의존해서는 안되며 저수준 세부사항이 고수준 컴포넌트에 의존성을 가져야 한다고 설명하고 있다.

 

소프트웨어를 개발하다보면 변경은 필연이기 때문에 변경하기 쉬운 코드를 작성해야한다. 고수준과 저수준 사이에 경계를 긋자. 고수준은 저수준 세부사항을 모르므로, 세부 사항을 자유롭게 변경해도 고수준 컴포넌트에는 영향을 주지 않는다. 이렇듯 DIP를 제대로 이해하고 있다면 변경하기 쉬운 코드를 작성할 수 있다.

 

참고

그림 1 출처 : https://www.oreilly.com/library/view/head-first-design/0596007124/ch01.html

반응형