035. (Clean Architecture) 11. DIP - 의존성 역전 원칙

11. DIP - 의존성 역전 원칙

마지막으로 SOLID의 D에 해당하는 의존성 역전 원칙(Dependency inversion principle) 이다.

의존성 역전 원칙에서 말하는 “유연성이 극대화된 시스템” 이란 소스 코드 의존성이 추상(abstarction)에 의존하며 구현체(concretion)에는 의존하지 않는 시스템이다.

다만 이 아이디어를 규칙으로 보기엔 확실히 비현실적이다.

소프트웨어 시스템이라면 구체적인 많은 장치에 반드시 의존하기 때문이다.

예를 들어 자바의 String은 구현체 클래스이며, 이를 추상 클래스로 만드는 것은 사실상 불가능하다.

java.lang.String 구현체 클래스에 대한 소스 코드 의존성은 벗어날 수도 없고, 벗어나서도 안된다.

반면 String 클래스는 매우 안정적이라고 볼 수 있다.

String 클래스가 변경되는 일은 거의 없으며, 있더라도 엄격하게 통제된다.

이러한 이유로 의존성 역전 원칙을 논의할 때, 운영체제나 플랫폼 같이 안정성이 보장된 환경에 대해서는 대체로 무시하는 편이다.

결론적으로 우리가 의존하지 않도록 피하고자 하는 것은 변경이 큰 구체적인 요소이며, 정확히는 우리가 개발하고 있어서 자주 변경될 수 밖에 없는 모듈들이 그 대상이다.

11.1. 안정된 추상화

추상 인터페이스에 변경이 생기면 이를 구체화한 구현체들도 따라서 수정해야 한다.

반대로 구체적인 구현체에 변경이 생기더라도 그 구현체가 구현하는 인터페이스는 항상(좀 더 정확히는 대부분) 변경될 필요가 없다.

따라서 인터페이스는 구현체보다 변동성이 낮다.

따라서 좋은 소프트웨어 설계를 위해서는 인터페이스의 변동성을 낮추기 위해 노력해야 한다.

즉, 안정된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 지양하고, 안정된 추상 인터페이스를 선호하는 아키텍처라는 뜻이다.

의존성 역전 원칙에서 전달하려는 내용은 아래와 같이 매우 구체적인 실천법으로 요약된다.

변동성이 큰 구현체 클래스를 참조하지 말라

구체 클래스 대신 추상 인터페이스를 참조하라.

이 규칙은 언어가 정적 타입이든 동적 타입이든 관계없이 모두 적용된다.

또한 이 규칙은 객체 생성 방식을 강하게 제약하며, 일반적으로 추상 팩토리르 사용하도록 강제한다.

변동성이 큰 구현체 클래스로부터 파생하지 말라

이 규칙은 이전 규칙의 따름 정리이다.

하지만 별도로 언급할 정도로 중요한 부분은 상속 때문이다.

정적 타입 언어에서 상속은 소스 코드에 존재하는 모든 관계 중에서 가장 강력하면서도 변경하기 어려운 관계이다.

따라서 상속은 아주 신중하게 사용해야 한다.

동적 타입 언어라고 해도 문제점이 될 가능성이 줄어들 뿐, 의존성을 가진다는 사실에는 변함이 업삳.

구현체 함수를 오버라이드 하지말라

대체로 구현체 함수는 소스 코드 의존성을 필요로 한다.

따라서 구체 함수를 오버라이드 하면 이러한 의존성을 제거할 수 없게되며, 실제로는 그 의존성을 상속하게 된다.

이러한 의존성을 제거하려면 추상 함수를 선언하고 구현체들에서 각자의 용도에 맞게 구현해야 한다.

구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라

이 규칙은 의존성 역전 원칙을 다른 방식으로 풀어쓴 것이다.

11.2. 팩토리

위에서 나열한 규칙을을 준수하려면 변동성이 큰 구체적인 객체는 특별히 주의해서 생성해야 한다.

사실상 모든 언어에서 객체를 생성하느 경우, 해당 객체를 구체적으로 정의한 코드에 대해 소스 코드 의존성이 발생한다.

이러한 의존성 문제를 해결하기 위해 대다수의 객체지향 언어에서는 추상 팩토리를 사용하곤 한다.

아래는 의존성을 관리하기 위한 추상 팩토리 메서드의 예시이다.

Use of the Abstract Factory pattern to manage the dependency

위의 빨간 색 곡선은 아키텍처의 경계를 뜻하며, 이 경계를 기준으로 구체적인 것과 추상적인 것들이 분리된다.

또한 이 곡선을 기준으로 시스템은 두 가지 컴포넌트로 분리되는데, 하나는 추상 컴포넌트이고 다른 하나는 구현체 컴포넌트이다.

추상 컴포넌트는 애플리케이션의 모든 고수준 업무 규칙을 포함하고, 구현체 컴포넌트는 업무 규칙을 다루기 위한 모든 세부사항을 포함한다.

이때 제어의 흐름은 소스 코드의 의존성과는 정반대로 방향으로 흐르게 되며 이 현상을 의존성 역전(Dependency Inversion) 이라고 부른다.

11.3. 구현체 컴포넌트

사실 위의 그림의 구현체 컴포넌트에는 구체적인 의존성인 ConcreteImpl이 존재한다.

즉, 의존성 역전 원칙에 위배된다.

사실 이는 일반적인 현상이고, 의존성 역전 원칙을 완전히 없앨 수는 없다.

다만 의존성 역전 원칙을 위배하는 클래스들은 적은 수의 구현체 컴포넌트의 내부로 모을 수 있고, 이를 통해 시스템의 나머지 부분과 분리할 수 있다.