[Chapter 11] 의존 관계 역전 원칙 (DIP)

Seungjae·2022년 1월 22일
0

의존 관계 역전 원칙(DIP)


  1. 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.
  2. 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.

어떤 것이 그렇다면 상위 수준의 모듈일까? 중요한 정책 의사결정과 업무 모델을 포함하는 것이 상위 수준의 모듈이다. 즉 상위 수준의 모듈은 애플리케이션의 본질을 담고 있다. 이런 상위 수준 모듈이 구체적 구현을 포함한 하위 수준의 구현에 의존하게 되면 직접적인 영향을 받으며 상위 수준의 모듈이 변경될 수 있다. 즉 하위 수준의 모듈에 영향을 주어야하는 것은 정책을 결정하는 상위 수준의 모듈이기에 의존성 방향은 "상위 <- 하위"로 향해야하며 상위 수준의 모듈은 독립적이어야 한다. 왜냐하면 우리가 일반적으로 재사용하기를 원하는 모듈은 어플리케이션의 본질인 상위 수준의 모듈이기 때문이다. 상위 수준의 모듈이 하위 수준의 모듈에 독립적이라면, 쉽게 이 상위 수준의 모듈을 간단히 재사용할 수 있다. 이러한 DIP원칙은 프레임워크 설계의 핵심이다.

레이어 나누기 -> 🔑 Key Point


  1. 잘 구조화된 모든 객체 지향 아키텍처를 레이어를 분명하게 정의하고, 각 레이어는 잘 정의되고 제어되는 인터페이스를 통해 일관된 서비스의 집합을 제공한다.
  2. 의존성은 이행적이다.
  3. 각 상위 수준 레이어가 그것이 필요로 하는 서비스에 대한 추상 인터페이스를 선언하게 하는 것이 좋다.
  4. 상위 수준 레이어는 하위 레이어에 의존하지 않고 추상 인터페이스를 통해 다음 하위 수준의 레이어를 사용한다.
  5. 하위 레이어는 상위 레이어에 선언된 추상 서비스 인터페이스에 의존한다.

소유권의 역전


DIP에서는 의존성의 방향 뿐만 아니라, 인터페이스 소유권에 대한 부분도 잘 살펴봐야한다. DIP가 적용된 경우, 클라이언트가 추상 인터페이스를 소유하는 경향이 있고, 이들의 서버가 그것에서 파생해 나오게 된다. 그래서 그런지 책의 예에서도 해당 추상 인터페이스의 클라이언트인 상위 레이어를 기준으로 인터페이스가 네이밍 된 것을 볼 수 있었다. 이렇게 DIP를 이용해서 소유권 또한 역전 시키면 하위 레이어의 변경에 상위 레이어가 영향을 받지 않을 수 있고, 상위 수준의 모듈을 어떤 문맥에서든 재사용할 수 있게 된다. 이렇게 더 유연하고, 튼튼하고, 이동이 쉬운 구조를 가지게 되는 것이다.

추상화에 의존하자


DIP는 간단히 해석하자면 "추상화에 의존하자"라고 할 수 있다. 구체 클래스에 의존하는 것이 아닌 추상화된 클래스나 인터페이스에 의존하라는 것이다. 이 해석에 따라서 아래와 같은 규칙들이 나오게 된다.

  • 어떤 변수도 구체 클래스에 대한 포인터나 참조값을 가져선 안된다.
  • 어떤 클래스도 구체 클래스에서 파생되어서는 안된다.
  • 어떤 메서드도 그 기반 클래스에서 구현된 메서드를 오버라이드해서는 안된다.

하지만 이 해석은 절대적인 것은 아니다. 구체적인 클래스여도 너무 많이 변경되지 않고, 다른 비슷한 파생 클래스가 만들어지지 않는다면, 이것에 의존해도 큰 문제는 발생하지 않는다. 또한 주의해야하는 점은 그저 이러한 해석만 따라서 변경이 많은 구체 클래스를 그러한 휘발성을 숨기기 위해 추상 클래스로 숨겨두면 안된다. 클라이언트가 필요로 하는 인터페이스를 뽑아내고 해당 인터페이스로 DIP를 실현해야함에 주의하자.

기타 사례 -> 🔑 Key Point


  1. 한 클래스가 다른 클래스에 메시지를 보내는 장소라면 어디든 DIP는 적용될 수 있다.
  2. 상위 수준의 정책이란 애플리케이션에 내재하는 추상화이자, 구체적인 것이 변경되더라도 바뀌지 않는 진실이다.
  3. 좀 더 넓게 클라이언트를 생각한다면 이름에서도 의존성이 있는 부분을 찾아낼 수 있다. 그리고 그러한 이름을 좀 더 적절하게 수정함으로써 그 의존성을 제거할 수 있다.
  4. 3번과 같은 경우 여러 클라이언트가 사용하게 되는, 아무도 소유하지 않는 인터페이스가 나오게 된다. 이 경우 여러 클라이언트에 의해 사용되고, 여러 서버에 의해 구현된다. 이 인터페이스는 어디에도 속하지 않는다. 즉 개별적인 패키지로 분리될 필요가 있다.
  5. 추상 클래스, 인터페이스를 사용하는 것을 동적 다형성이라고 하고, C++의 템플릿을 사용하는 것을 정적 다형성이라고 한다.

결론


전통적인 절차 지향 프로그래밍은 정책이 구체적인 것에 의존하는 구조를 만든다. 객체 지향 프로그래밍은 이런 의존성 구조를 역전시켜 구체적인 사항과 정책이 모두 추상화에 의존하고, 클라이언트가 그 서비스 인터페이스를 소유하게 한다.

좋은 객체 지향 설계의 증명은 이러한 의존성이 잘 역전이 됐는가라고 볼 수 있다. 의존성이 역전되어 있다면 객체 지향 설계에 가깝고, 그렇지 않다면 절차 지향에 가깝다.

재사용 가능한 모듈, 프레임워크를 만들기 위해서는 이러한 DIP 응용이 필수적이며, 변경에 탄력적인 코드를 작성하는데 있어 결정적으로 중요하다. 또한 추상화와 구체적인 사항이 분리되어 있기에, 유지보수하기도 더 용이하다.

profile
코드 품질의 중요성을 아는 개발자 👋🏻

0개의 댓글

관련 채용 정보