들어가기에 앞서...
지난 글에서는 DIP 원칙이란 무엇인지, 또 DIP의 장점과 적용하는 방법에 대해서 설명했다. 해당 원칙대로 작성한 코드는 순환 참조 문제에 빠질 일이 없고 객체지향적으로써 바람직한 코드가 될 것이다.
DIP 원칙 그 자체로써는 단점이 거의 없다.(아주 없지는 않다. 코드의 길이가 살짝 늘어난다. 물론 이러한 단점은 장점에 비하면 매우 사소하다.) 이 글의 제목이 '단점'이 아닌 '한계'라는 사실에 주목하라.
그렇다면 이 글에서 설명하고자 하는 한계는 무엇인가? 바로 DIP 원칙을 적용할 수 없는 경우이다. 상위 폴더에 인터페이스를 두기만 하면 DIP 원칙이 적용된다고 생각할 수 있고 또 어느 정도는 사실이지만, 이것이 불가능한 경우가 존재한다. 이 글에서는 DIP 원칙을 적용할 수 없는 경우와 해당 상황에서의 대안에 대해 설명하고자 한다.
해당 글은 Go언어의 관점에서 바라보았기 때문에 다른 언어의 관점에서 이해되지 않는 부분이 존재할 것이다. 아래의 한계나 대안이 자신의 프로그래밍 언어에 꼭 맞지 않는다면 다른 항목을 참조하거나 새로운 방법을 떠올려 보는 것도 이 글을 즐기는 하나의 방법일 것이다.
어플리케이션 레벨
어플리케이션의 경우 보통 main 패키지가 최상단에 위치한다. 그리고 모든 프로그램은 결국 인터페이스가 아닌 구현체를 이용해 작동한다. 문제는 구현체를 사용해야 할 main 패키지가 최상위 패키지이고, 구현체들은 최하위 패키지라는 것이다. DIP 원칙에서 각 패키지는 바로 위의 상위 패키지를 참조하는데, 이 관점에서 볼 때 main 패키지와 최하위 패키지의 관계는 DIP 원칙과 대척점에 있다.
하지만 DIP 원칙의 원래 목적을 생각해보면 main 패키지에서 하위 패키지들을 참조하는 구조에는 아무런 문제가 없다. main 패키지가 다른 패키지를 참조할 수는 있어도 프로젝트 내의 다른 패키지들은 main 패키지를 참조할 수 없다. 즉, main 패키지는 다른 패키지와 순환 참조가 생길 여지가 없다. 즉, main 패키지에 한해서는 DIP 원칙을 적용할 수도 없고, 필요도 없다.
공용 구조체
프로그래밍을 하다 보면 여러 패키지에서 같이 쓰는 객체가 존재한다. 해당 객체가 인터페이스로 존재하면 문제 없이 DIP 원칙을 적용할 수 있다. 문제는 그 객체가 구조체로 정의하는 것이 더 깔끔하고 의미적으로 맞는 경우이다. 물론 인터페이스로 대채하는 것도 방법이지만 인터페이스가 오히려 복잡성을 증가시키는 경우에는 구조체를 사용하는 것이 좋다.
여러 군데에서 쓰는 구조체들은 별개의 패키지나 프로젝트를 만들어 그 곳에 넣으면 된다. 이 때 주의할 것은 해당 패키지에서 본 프로젝트의 다른 패키지를 참조해서는 안된다.(물론 해당 패키지 내부적으로는 DIP 원칙을 적용하면서 잘 참조해도 된다.) 이는 순환 참조를 막기 위함이다. 위의 항목과는 달리 문법 단계에서 막을 수 없기 때문에 프로그래머의 주의를 요한다. 이렇게 구성된 공용 구조체는 다른 패키지를 참조할 수 없기 때문에 비교적 크기가 작다.
API
패키지는 보통 최상위 패키지에서 일반화된 api를, 하위 패키지에서 일반적이지 않은, 좀 더 세부적인 api를 제공한다. go언어의 기본 라이브러리만 살펴봐도 알 수 있다. 즉, 사용자는 일반적으로 최상위 패키지의 api를 이용하고 필요에 따라 하위 패키지를 참조한다. 하지만 DIP 원칙은 이와 대척점에 있다. DIP 원칙을 충실히 따르는 코드는 구현체와 생성자가 최하위 패키지에 위치하기 때문에 사용자 역시 최하위 패키지를 이용할 수 밖에 없다. 위의 항목들이 단순히 개발하는 입장에서 불편한 정도였다면 해당 항목은 사용하는 입장에서 불편한 상황이다. 사실상 DIP 원칙의 가장 큰 한계라고 할 수 있다.
첫번째 항목과 비교하면 재미있을 것이다. 원인 자체는 꽤나 비슷한 상황이기 때문이다. 하지만 이번에는 첫번째 항목의 방식대로 해결할 수 없다. 최상위 패키지가 main 패키지가 아니라는 것은 하위 패키지에서 얼마든지 최상위 패키지를 이용할 수 있고, 이는 순환 참조의 위험이 있다는 것을 뜻한다. 해결법은 두번째 패키지와 비슷한데, 아예 api만을 위한 프로젝트를 만드는 것이다. 물론 원래 프로젝트 내에 api를 위한 하위 패키지를 만드는 방법도 있지만, 필자는 api를 항상 최상위 패키지에 두는 것이 가장 바람직하다고 생각한다.
마치며...
이번 글에서는 DIP 원칙의 한계와 대처법에 대해 알아보았다.
마치기 전에 필자의 토이 프로젝트를 소개하고자 한다. nonogram 프로젝트는 흔히 노노그램(네모로직, 피크로스 등 다양한 이름이 있다)이라 불리는 퍼즐 게임을 패키지화 시켜놓은 프로젝트이다. 나름 필자가 DIP 원칙을 포함한 여러 OOP 기법을 이용했다. DIP 원칙을 어떻게 적용해야 하는지 모를 때 해당 프로젝트를 참고하면 도움을 얻을 수 있을 것이다. 기여도 언제나 환영이다. 여담으로 해당 패키지를 이용해 CLI 환경에서 노노그램을 플레이 할 수 있는 nonograminGo 프로젝트(사실 해당 프로젝트는 스파게티 코드 투성이라 공부하기에 적합하지 않다.)도 있다.
마지막으로, 해당 글에 대해 다른 의견을 갖고 있거나 질문이 있으면 댓글로 남겨주길 바란다.