[Clean Architecture] CH11 DIP(의존성 역전 원칙)

2 minute read


DIP(의존성 역전 원칙)

  • DIP에서 말하는 유연성이 극대화된 시스템이란 소스코드 의존성이 추상(abstract)에 의존하며 구체(concretion)에는 의존하지 않는 시스템임
  • 자바와 같은 정적 타입 언어에서 이 말은 use, import, include 구문은 오직 인터페이스나 추상 클래스 같은 추상적인 선언만을 참조해야 한다는 뜻임
  • 파이썬과 같은 동적 타입 언어에도 동일한 규칙이 적용됨. 즉 소스 코드 의존 관계에서 구체 모듈을 참조해서는 안 됨
    • 하지만 이들 언어의 경우 구체 모듈이 무엇인지를 정의하기 다소 어려움
  • 정적 언어에서도 소프트웨어 시스템은 필연적으로 많은 장치에 반드시 의존하기 마련임(예를 들어 자바에서 String은 구체 클래스임)
  • 하지만 String 클래스는 매우 안정적이며, 변경이 자주 발생하지 않음
  • 그렇기에 DIP를 논할 떄 운영체제나 플랫폼 같이 안정성이 보장된 환경에 대해서는 무시할 수 있음
  • 의존을 피할 요소는 변동성이 큰 구체적인 요소로, 보통 현재 개발중인 부분들임

안정된 추상화

  • 인터페이스는 대다수의 경우 구현체보다 변동성이 낮음
  • 따라서 뛰어난 아키텍트라면 구현체에 기능을 추가할 수 있는 방법을 찾으며, 이를 통해 인터페이스의 변동성을 낮춤
  • 즉, 안정된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 지양, 안정된 추상 인터페이스를 선호해야 함
  • 이는 다음의 구체적인 코딩 실천법으료 요약할 수 있음
    • 변동성이 큰 구체 클래스를 참조하지 말 것
      • 구체 클래스 대신 추상 인터페이스를 참조할 것
      • 이는 정적/동적 언어에 상관없이 적용됨
      • 이 규칙은 객채 생성 방식을 강하게 제약하며, 일반적으로 추상 팩토리를 사용하도록 강제함
    • 변동성이 큰 구체 클래스로부터 파생하지 말 것
      • 상속은 소스 코드에 존재하는 모든 관계 중 가장 강력한 동시에 변경하기 어려움
      • 따라서 상속은 아주 신중하게 사용되어야함.
    • 구체 함수를 오버라이드 하지 말 것
      • 대체로 구체 함수는 소스 코드 의존성을 필요로 함
      • 따라서 구체 함수를 오버라이딩하면 이러한 의존성을 제거할 수 없으며, 실제로는 그 의존성을 상속하게 됨
      • 이러한 의존성을 제거하려면, 추상 함수를 선언하고 구현체들에서 각자의 용도에 맞게 구현해야 함

팩토리

  • 위 규칙들을 준수하려면 변동성이 큰 구체적인 객체는 특별히 주의해서 생성해야 함
  • 이는 당연하건데, 사실상 모든 언어에서 객체를 생성하려면 해당 객체를 구체적으로 저의한 코드에 대해 의존성이 발생하기 떄문
  • 이러한 의존성을 해결하기 위해 추상 팩토리를 사용할 수 있음

  • 위 그림의 곡선은 아키텍처 경계를 뜻하며, 해당 곡선은 구체적인 것들로부터 추상적인 것들을 분리함
  • 소스 코드 의존성은 해당 곡선과 교차할 떄 모두 한 방향, 즉 추상적인 쪽으로 향함
  • 이렇듯 추상 컴포넌트와 구체 컴포넌트는 분리되어야 함
    • 추상 컴포넌트는 애플리케이션의 모든 고수준 업무 규칙을 포함해야 함
    • 구체 컴포넌트는 업무 규칙을 다루기 위해 필요한 모든 세부사항을 포함해야 함
  • 그림에서 소스 코드 의존성은 제어흐름과는 반대 방향으로 역전됨, 이러한 이유로 해당 원칙을 의존성 역전(Dependency Inversion)이라고 함

구체 컴포넌트

  • 위 그림에서 구체 컴포넌트에는 구체적인 의존성이 있음(ServiceFactoryImpl 구체 클래스가 ConcreteImpl 클래스에 의존)
  • 이는 DIP에 위배되지만, DIP 위배를 모두 없앨 수는 없기에 일반적인 일임
  • 중요한 것은 DIP를 위배하는 클래스들을 적은 수의 구체 컴포넌트 내부로 모으고, 이를 시스템의 나머지 부분과 분리하는 일임
  • 대다수의 시스템은 이러한 구체 컴포너트를 최소한 하나는 포함함.
    • 흔히 이 컴포넌트는 메인(Main)이라고 불리며, main 함수를 포함함
    • 위 그림에서 main 함수는 ServiceFactoryImpl의 인스턴스를 생성, 이 인스턴스를 ServiceFactory 타입으로 전역 변수에 저장할 것임
    • 그럼 다음 Application에서 이 전역 변수를 이용해 ServiceFactoryImpl 인스턴스에 접근할 것임