본문 바로가기

Dependency Injection

DependencyInjection - What, Why, and How(Understanding Perpose of DI, Petterns)

728x90

Understanding Perpose of DI

앞선 게시물 에서 말했듯 DI는 최종 목표가 아니라 목적을 위한 수단일 뿐이다. 

DI는 느슨한 결합을 가능하게 하고 이는 코드를 더 쉽게 유지관리 할 수 있게 만든다.

이 중요한 메시지를 전달하기 위해 소프트웨어 설계와 몇 가지 설계패턴을 전기 배선과 비교해 보겠다. 매우 쉬운 비유를 들 예정이므로 비전문가에게 소프트웨어 설계를 설명할 때도 이 비유를 사용하면 좋다. 

아래에서 설명하겠지만 이 비유에서는 네 가지 특정 디자인 패턴을 자주 언급할 것이다. 그 이유는 이패턴들이 DI와 관련하여 자주 발생하기 때문이다.
이 책에서는 이 중 세 가지 패턴인 데코레이터, 컴포지트, 어댑터 에 대한 예시를 많이 볼 수 있다.

 

Checking Into a Cheap Hotel

저렴한 호텔에 묵는다면 벽면 콘센트에 직접 연결해놓은 헤어드라이어를 볼 수 있다. (한국에는 없겠지만?) 
어쨌거나, 고객의 편의를 위해 제공했지만 호텔 측에선 이러나 저러나 문제되는 상황에 부딪히기 쉽다.
도난당하는것을 방지하기 위해 이런 방법을 쓴 것이지만, 만약 헤어 드라이어가 작동을 멈춘다면? 호텔 측에선 숙련된 전문가를 불러야 할 것이다.
유선으로 연결된 헤어 드라이어를 수리하려면 객실의 전원을 차단해야 하므로 일시적으로 사용할 수 없게 된다.  숙련된 전문가는 헤어드라이어의 연결을 끊고 새 것으로 교체할 것이다. 
이 시나리오를 보니 어떠한가? 문제가 명확히 보인다. 헤어 드라이어가 벽에 단단히 결합되어 있어 다른 쪽에 영향을 주지 않고는 쉽게 수리(수정)할 수 없다는 것이다.

 

Comparing Electrical Wiring to Design Patterns

일반적으로 우린 케이블을 벽에 직접 연결하지 않는다. 우린 너무나 당연하듯, 플로그와 소켓을 사용하고 있다.

소켓은 플러그가 일치해야 하는 모양을 정의한다.
소프트웨어 설계에 비유하자면, 소켓은 인터페이스가 될 것이고, 플러그는 구현이다. 


벽면 콘센트와 직접 연결한 헤어 드라이어와 달리, 플러그와 소켓은 전기 제품을 연결하기 위한 느슨한 결합의 모델을 정의한 것이다.
플러그(구현)가 소켓에 맞고(인터페이스), 전압과 헤르츠의 양을 처리할 수 있다면(인터페이스 준수) 다양한 방식으로 가전제품을 결합할 수 있다.

특히 흥미로운 점은 이러한 일반적인 조합 중 상당수가 잘 알려진 소프트웨어 설계 원칙 및 패턴과 비교할 수 있다는 것이다.
이제 우리는 더 이상 헤어 드라이어에 국한되지 않는다. 그저 헤어 드라이어의 플러그를 뽑고 컴퓨터를 같은 소켓에 꽂으면 된다.

소켓의 개념이 컴퓨터보다 수십 년이나 앞선 개념임에도 불구하고 컴퓨터에 필수적인 서비스를 제공한다는 것은 놀라운 일이다. 소켓의 최초 설계자는 개인용 컴퓨터를 예측할 수 없었지만, 소켓의 디자인이 매우 다재다능하기 때문에 원래는 예상하지 못했던 요구 사항을 충족할 수 있었다.
소켓이나 인터페이스를 변경하지 않고도 플러그 또는 구현을 전환할 수 있는 능력은 리스코프 치환 원칙라는 중앙 소프트웨어 설계 원칙과 유사하다. 이 원칙은 클라이언트나 구현을 손상시키지 않고 인터페이스의 한 구현을 다른 구현으로 대체할 수 있어야 한다는 것이다.
DI와 관련하여 리스코프 치환은 가장 중요한 소프트 웨어 설계 원칙 중 하나다. 이 원칙을 통해 현재 예측할 수 없더라도 미래에 발생할 수 있는 요구사항을 해결할 수 있다.

 

지금 당장 컴퓨터를 사용할 필요가 없다면 플러그를 뽑아 두면 그만이다. 즉 컴포넌트를 벽에서 뽑아도 벽면 콘센트나 컴퓨터가 고장나지 않는다는 말이다.

이 외에도 할 수 있는 일은 많다. 간헐적으로 정전이 발생하는 지역에 거주하는 경우 무정전 전원 공급 장치(UPS. Uninterruptible Power Supply)에 연결하여 컴퓨터를 계속 작동 시키고 싶을 수 있다. 그림처럼 UPS를 벽면 콘센트에 연결하고 난 뒤 컴퓨터를 UPS에 연결한다.

 


컴퓨터와 UPS는 서로 다른 용도로 사용된다. 각각은 다른 장치를 침해하지 않는 단일 책임이 있다. 
소프트웨어 설계에서 동일한 인터페이스의 다른 구현으로 한 구현을 가로채는 방식을 데코레이터 설계 패턴이라고 한다. 
이 패턴을 사용하면 기존 코드를 많이 다시 작성하거나 변경하지 않고도 새로운 기능을 도입할 수 있다.

기존 코드베이스에 새로운 기능을 추가하는 또 다른 방법은 인터페이스의 기존 구현을 새로운 구현으로 리팩터링 하는 것이다. 여러 구현을 하나로 통합할 때는 컴포지트 디자인 패턴을 사용한다. 다양한 기기를 멀티탭에 꽂는 경우가 그 예가 될 것이다.

멀티탭에는 하나의 소켓에 꽂을 수 있는 하나의 플러그가 있고, 멀티탭 자체는 다양한 기기를 위한 여러개의 소켓을 제공한다. 따라서 컴퓨터가 작동하는 동안 헤어 드라이어를 추가하고 제거할 수 있다. 마찬가지로 컴포지트 패턴을 사용하면 구성된 인터페이스 구현 집합을 수정하여 기능을 쉽게 추가하거나 제거할 수 있다.

마지막 예시가 있다. 플러그가 특정 콘센트에 맞지 않는 상황인 경우. 그러니까, 다른 나라를 여행해봤다면 나라마다 콘센트가 다르다는 것을 알 수 있다. 여행할 때 휴대폰 보조배터리나, 노트북 등등 기기를 가지고 갔다면 충전할 어댑터가 필요할것이다. 적절하게도 같은 이름의 디자인 패턴이 있다.

 

 

어댑터 디자인 패턴은 실제 이름처럼 작동한다. 어댑터 패턴은 애플리케이션이 사용하는 인터페이스의 인스턴스로 노출하려는 기존 API가 있을 수 있을때 유용하게 쓸 수 있다. 물리적 어댑터와 마차가지로 어댑터 디자인 패턴의 구현은 단순한 것부터 매우 복잡한 것까지 다양하다.

지금까지 알아본 소켓 및 플러그 모델의 놀라운 점은 수십년 동안 다재다능한 모델임을 입증했다는 것이다. 일단 인프라가 구축되면 누구나 사용할 수 있으며 변화하는 요구사항과 예상치 못한 요구사항에 적응할 수 있다. 더 흥미로운 점은 이 모델을 소프트웨어 개발과 연관시켜 보면 모든 구성요소가 디자인 원칙과 패턴의 형태로 이미 마련되어 있다는 것이다.

느슨한 결합의 장점은 물리적 소켓 및 플러그 모델에서와 마찬가지로 소프트웨어 설계에서도 동일하게 적용된다. 일단 인프라가 구축되면 애플리케이션 코드 베이스와 인프라를 크게 변경하지 않고도 누구나 사용할 수 있고 변화하는 요구사항과 예상치 못한 요구사항에 맞게 조정할 수 있다. 
즉, 새로운 요구사항이 생기면 시스템의 다른 기존 클래스는 변경하지 않고 새 클래스만 추가하면 되는 것이 이상적인 그림이다.

기존 코드를 수정하지 않고도 애플리케이션을 확장할 수 있다는 개념은  개방형/폐쇄형 원칙이라고 한다. 
코드의 100%가 항상 확장성을 위해 열려 있고 수정을 위해 닫혀 있다는 이 원칙에 도달하는것은 사실상 불가능에 가까울 정도로 매우 어려울 수 있다. 하지만 느슨한 결합이 목표에 더 가까이 다가갈 수 있게 해준다.

그리고 매 단계마다 시스템에 새로운 기능과 요구사항을 추가하는 것이 더 쉬워진다. 시스템의 기존 부분을 건드리지 않고 새로운 기능을 추가할 수 있다는 것은 문제가 격리되어 있다는 것을 의미한다. 이는 이해와 테스트가 더 쉬운 코드로 이어져 시스템의 복잡성을 관리할 수 있게 해준다.
개방/폐쇄 원칙에 대해선 나중에 자세히 알아보자.

728x90