본문 바로가기

ComputerScience/DesignPattern

[SOLID] DIP: The Dependency-Inversion Principle


*
이 글은Agile Software Development, Principles, Patterns, and Practices - Robert Martin 책 내용을 번역 및 요약하여 작성하였습니다.

DIP: The Dependency-Inversion Principle

a. High-level modules should not depend on low-level modules. Both should depend on abstractions. 
b. Abstractions should not depend on details. Details should depend on abstractions.

고수준 모듈이 저수준 모듈에 의존하는 경우 이러한 고수준 모듈을 다른 맥락에서 재사용하기가 매우 어려워집니다. 그러나 고수준 모듈이 저수준 모듈과 독립적인 경우 고수준 모듈을 간단히 재사용할 수 있습니다. 이것이 The Dependency-Inversion Principle의 핵심입니다.

 

(여기서 '고수준 모듈'은 쉽게 말해, 자바에서 Interface, abstract 클래스이고 '저수준 모듈'은 그 Interface, abstract를 구현 및 상속한 구체적인 클래스라고 생각하면 됩니다.)

Layering

Figure 11-1의 다이어그램에서 고수준 Policy 계층이 하위 수준 Mechanism 계층을 사용하고, Mechanism 계층이 다시 상세 수준 Utility 계층을 사용하는 것으로 보입니다.

이는 적절해 보일 수 있지만, Policy 계층이 Utility 계층까지 내려가는 것은 '변경'에 민감한 특성을 갖습니다. 

의존성은 전이적입니다. Policy 계층은 Utility 계층에 의존하는 것에 의존하고, 따라서 Policy 계층은 전이적으로 Utility 계층에 의존합니다. 즉 어떠한 변경이 발생하면, 전이적으로 하위 레이어까지 모두 변경이 필요한 상황이 발생합니다. 이는 매우 불안정한 구조입니다.

An Inversion of Ownership

Figure 11-2는 더 적절한 모델을 보여줍니다. 각 상위 계층은 필요한 서비스에 대한 추상 인터페이스를 선언합니다. 그런 다음 하위 계층은 이러한 추상 인터페이스를 통해 실현됩니다. 각 상위 계층 클래스는 추상 인터페이스를 통해 다음 낮은 계층을 사용합니다.

따라서 상위 계층은 하위 계층에 의존하지 않습니다. 대신 하위 계층은 상위 계층에서 선언한 추상 서비스 인터페이스에 의존합니다.

 

PolicyLayer가 UtilityLayer에 대한 전이적 의존성이 사라지는 것뿐만 아니라 PolicyLayer가 MechanismLayer에 대한 직접적인 의존성도 사라집니다. 이로써 PolicyLayer는 MechanismLayer 또는 UtilityLayer의 변경에 영향을 받지 않습니다.

더욱이, PolicyLayer는 PolicyServiceInterface를 준수하는 하위 수준 모듈을 정의하는 모든 문맥에서 재사용될 수 있습니다.

 

따라서 의존성을 역전시킴으로써 더 유연하고 견고하며 이식성이 뛰어난 구조를 만들었습니다.

Dependency-Inversion에서 설명하는 Inversion(역전)은 의존성뿐만 아니라 인터페이스 소유권의 역전도 있는 것을 알아야 합니다.

DIP가 적용되면 클라이언트가 추상 인터페이스를 소유하고 그 서버가 이를 파생시키는 경향이 있습니다.

Depend On Abstractions

DIP의 단순하면서 강력한 해석 중 하나는 "추상화에 의존"이라는 간단한 아이디어입니다.

이 아이디어를 간단하게 설명하면 프로그램의 모든 관계가 추상 클래스나 인터페이스에서 끝나야 한다고 권장합니다.

이 아이디어에 따르면 다음과 같은 사항이 해당됩니다.

  • 어떤 변수도 이미 구현된 클래스의 포인터 또는 참조를 보유해서는 안 됩니다.
  • 어떤 클래스도 이미 구현된 클래스에서 파생되어서는 안 됩니다.
  • 어떤 메서드도 기본 클래스의 구현된 메서드를 재정의해서는 안 됩니다.

물론 이 아이디어는 대개 모든 프로그램에서 최소한 한 번은 어길 것입니다. 누군가는 구현된 클래스의 인스턴스를 생성해야 하며, 그 작업을 수행하는 모듈은 해당 클래스에 의존할 것입니다. 더구나, 구현된 클래스가 변화하기 어려운 경우에는 이 아이디어를 따를 이유가 없어 보입니다. 만약 이미 구현된 클래스가 크게 변하지 않고 다른 유사한 파생 클래스가 생성되지 않을 것이라면 그 클래스에 의존하는 것은 크게 문제가 되지 않을 것입니다.

A Simple Example

위의 도식화는 Button 클래스가 Lamp 클래스에 직접 의존하는 것을 볼 수 있습니다. 이 종속성은 Button이 Lamp의 변경 사항에 영향을 받을 것을 의미하며, 더 나아가 Button을 사용하여 Motor 객체를 제어하는 것은 불가능할 것입니다. 이 디자인에서 Button 객체는 Lamp 객체만을 제어합니다.

이 해결책은 의존성 역전 원칙(DIP)을 위반합니다. 응용 프로그램의 고수준 정책이 저수준 구현으로부터 분리되지 않았습니다. 즉 추상화가 세부 사항으로부터 분리되지 않았습니다. 이러한 분리가 없으면 고수준 정책이 자동으로 저수준 모듈에 의존하고 추상화가 세부 사항에 자동으로 의존합니다.

Finding the Underlying Abstraction

Figure 11-3의 디자인은 의존성을 뒤집어서 개선할 수 있습니다. Figure 11-4에서는 Button이 이제 ButtonServer라는 것에 연관성을 가지고 있음을 볼 수 있습니다. ButtonServer는 Button이 무언가를 켜거나 끄는 데 사용할 수 있는 추상 메서드를 제공합니다. Lamp는 ButtonServer 인터페이스를 구현합니다. 따라서 Lamp가 의존하는 측이 되었으며 의존성을 뒤집었습니다.

Figure 11-4의 디자인은 Button이 ButtonServer 인터페이스를 구현하는 임의의 장치를 제어할 수 있게 합니다. 이로써 많은 유연성을 얻을 수 있습니다.

 

그러나 이 솔루션은 Button에 의해 제어되어야 하는 객체에 제약을 가합니다.

예를 들어 Button 이외의 Switch 객체나 Button이 아닌 다른 객체에 의해 제어되길 원하는 경우에 불편합니다. 이러한 객체는 ButtonServer 인터페이스를 구현해야 합니다. 

 

Lamp는 ButtonServer에 의존하지만 ButtonServer는 Button에 의존하지 않습니다. ButtonServer 인터페이스를 조작하는 방법을 알고 있는 모든 종류의 객체는 Lamp를 제어할 수 있을 것입니다. 따라서 이는 Lamp가 ButtonServer 인터페이스만 의존한다기엔 모호합니다.

이 문제는 ButtonServer의 이름을 더 일반적인 것으로 변경하여 해결할 수 있으며, SwitchableDevice와 같은 이름으로 변경할 수 있습니다. 또한 Button과 SwitchableDevice가 별도의 라이브러리에 유지되도록하여 SwitchableDevice의 사용이 Button의 사용을 의미하지 않도록 할 수 있습니다.

 

이 경우, 누구도 인터페이스를 소유하지 않습니다. 우리는 인터페이스가 다양한 클라이언트에서 사용되고 다양한 서버에서 구현되는 상황을 가지고 있습니다. 따라서 인터페이스는 어느 그룹에도 속하지 않으면서 독립적으로 존재해야 합니다. C++에서는 별도의 네임스페이스와 라이브러리에 넣을 것이고, Java에서는 별도의 패키지에 넣을 것입니다.

Conclusion

  • 전통적인 절차 지향 프로그래밍은 정책이 세부 내용에 의존하는 종속 구조를 생성합니다. 이것은 정책이 세부 내용의 변경에 취약해지는 불행한 상황입니다.
  • 객체 지향 프로그래밍은 이러한 종속성 구조를 뒤바꿈으로써 세부 내용과 정책 모두 추상화에 의존하며, 서비스 인터페이스는 종종 그 클라이언트에 의해 소유됩니다.
  • 실제로 종속성을 뒤바꾸는 것은 좋은 객체 지향 설계의 특징입니다. 프로그램이 어떤 언어로 작성되었는지는 중요하지 않습니다. 그 종속성이 뒤바뀌어 있다면, 그것은 객체 지향 설계를 가지고 있습니다. 그 종속성이 뒤바뀌지 않았다면, 그것은 절차적 설계입니다.
  • Dependency-Inversion(의존성 역전) 원칙은 객체 지향 기술로 주장되는 많은 이점의 근간에 있는 중요한 저수준 메커니즘입니다. 올바른 적용은 재사용 가능한 프레임워크를 만들기 위해 필요합니다. 또한 변경에 강한 코드를 구성하는 데 매우 중요합니다. 추상화와 세부 내용이 서로 격리되어 있기 때문에 코드를 유지 관리하기가 훨씬 쉽습니다.