설계원칙 - SOLID(DIP) S/W 설계

- 이 글은 로버트 C.마틴의 Clean Architecture를 기반으로 작성되었습니다. (가능하면 책을 읽어보는것을 추천한다.)
- 추상팩토리 패턴:https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81_%ED%8C%A9%ED%86%A0%EB%A6%AC_%ED%8C%A8%ED%84%B4

- 개요
DIP는 의존성 역전 원칙이다. 유연성이 극대화된 시스템이란 소스코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템이다.
단어가 어렵더라도 천천히 살펴보자. 우리가 좋아하는 스프링 웹 소스를 떠올려 보자. Controller에서 Service를 호출할 때, Service에 대한 인터페이스를 참조해야지 해당 인터페이스(추상클래스)를 구현한 구체 클래스를 참조해서는 안된다는 의미이다. 그렇다면 모든 구체 클래스를 참조하는것은 금기사항일까?
프로그램에서 String 클래스를 사용할 때 추상 계층으로 감싸진 않는다. String 도 구체 클래스인데 왜 그럴까? String은 변경되는 일이 거의 없으며, 우리가 변경할 가능성 자체가 희박하고 엄격한 규율에 따라 변경된다. DIP를 논할때에는 운영체제나 플랫폼과 같이 안정적인 환경에 대해서는 구체라도 용납하는편이다.

- 안정된 추상화
개요에서 언급했듯이 유연성있는 시스템을 만들기 위해서는 구체적인 구현체들이 아닌 추상계층에 의존해야 한다. 여기서 주의해야할점이 있는데, 추상계층만 참조하면 안정된 시스템이라고 생각하면서 인터페이스 메소드의 시그니처나 스펙을 마구 변경해서는 안된다. 그만큼 인터페이스를 설계할때는 신중해야 한다.
코딩룰에서 DIP를 지키기 위해 몇 가지 간단한 규칙을 살펴보자.
  • 변동성이 큰 구체 클래스를 참조: 여태까지 했던말을 왜 또 반복할까 라고 생각할수도 있다. 하지만 이 규칙을 객체 생성시에도 고려하면 new 를 남발하는 대신 추상 팩토리 패턴을 사용하는편이 좋다는 말이 된다. 인터페이스나 추상 클래스만 참조하고 난 할일을 다했다가 아니라 최대한 추상계층을 참조 하기 위해 어떻게 해야할지 늘 생각해보자.
  • 변동성이 큰 구체 클래스를 상속: 상속은 필요할때는 사용해야 하지만 사용할 떄에는 신중해야 한다. 의존성이 매우 크므로 상속은 사용할때에는 다시 한번 생각해보자.
  • 구체함수를 오버라이드: 구체 함수를 오버라이드 하면 의존성도 상속하게 된다.

- 팩토리
정적타입언어에서 변동성이 큰 객체를 생성할 때는 주의해야 한다. 이를 위해 추상 팩토리 패턴을 사용하곤 하는데, 아래는 해당 패턴을 다이어그램으로 표현한것이다.
위 다이어그램에서 Application의 목적은 ProductAImpl과 ProductBImpl 의 인스턴스를 사용하여 어떠한 행위를 하는것이다. 하지만 구체클래스에 직접 의존하지 않으면서 해당 목적을 이루기 위해서는 ProductFactory의 메소드들을 통해 객체를 생성해야 한다.
ProductFactoryImpl 에서 각각 ProductAImpl과 ProductBImpl의 인스턴스를 생성한 후, 각각 ProductA, ProductB 타입으로 반환한다.
Boundary 라고 표시된 점선은 추상 컴포넌트와 구체 컴포넌트의 경계이다. 위쪽은 추상컴포넌트라고 볼 수 있으며, 아래쪽은 구체컴포넌트라고 볼 수 있다. 
프로그램의 제어흐름은 Application에서 ProductAImpl과 ProductBImpl 이지만, 소스코드의 의존성은 이러한 방향과 반대 방향(구체 컴포넌트 -> 추상 컴포넌트 방향)으로 역전되고 있다. 이런 이유때문에 의존성 역전이라고 부른다.

- 구체 컴포넌트
위의 다이어 그램에서 ProductFactoryImpl 이 ProductAImpl과 ProductBImpl 에 의존하고 있다. 이 또한 DIP 위반이지만 이러한 위반을 모두 없앨 수는 없다.
핵심적인 가치인 업무규칙을 추상 컴포넌트에서 처리할 때, 추상 컴포넌트내에서는 DIP 위배를 없애기 위한 목적은 달성했다는데 의의가 있다.

- 결론
위 다이어그램에서 Boundary는 중요한 의미를 갖는다. 컴포넌트가 아키텍처의 경계가 되며 이 Boundary를 기점으로 소스코드의 의존성은 항상 구체 컴포넌트에서 추상 컴포넌트로, 저수준의 컴포넌트에서 고수준의 컴포넌트로 향해야 한다.


덧글

댓글 입력 영역