의존성 역전 원리는 무엇이며 왜 중요한가?


178

의존성 역전 원리는 무엇이며 왜 중요한가?



3
Wikipedia의 "높은 수준"및 "낮은 수준"용어를 사용하여 여기에 대한 엄청나게 많은 답변이 있습니다. 이 용어에 액세스 할 수 없으며 많은 독자들이이 페이지로 연결됩니다. 위키 백과를 역류시키려는 경우 답변에이 용어정의 하여 컨텍스트를 제공 하십시오 !
8bitjunkie

답변:


107

이 문서를 확인하십시오 : Dependency Inversion Principle .

기본적으로 말합니다 :

  • 고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.
  • 추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 의존해야합니다.

중요한 이유는 간단히 말해서 변경이 위험하고 구현이 아닌 개념에 따라 콜 사이트에서 변경의 필요성이 줄어든다는 것입니다.

효과적으로, DIP는 상이한 코드 조각들 사이의 결합을 감소시킨다. 아이디어는 로깅 기능을 구현하는 여러 가지 방법이 있지만이를 사용하는 방식은 시간이 비교적 안정적이어야한다는 것입니다. 로깅 개념을 나타내는 인터페이스를 추출 할 수있는 경우이 인터페이스는 구현보다 훨씬 안정적이어야하며 호출 사이트는 해당 로깅 메커니즘을 유지 관리하거나 확장하는 동안 수행 할 수있는 변경의 영향을 훨씬 덜받습니다.

또한 구현이 인터페이스에 종속되도록함으로써 런타임시 특정 환경에 더 적합한 구현을 선택할 수 있습니다. 경우에 따라 이것 또한 흥미로울 수 있습니다.


31
이 답변은 왜 DIP가 중요한지, 심지어 DIP가 무엇인지 말하지 않습니다. 나는 공식적인 DIP 문서를 읽었으며, 이것이 잘못 가정 된 것에 기초하고 있기 때문에 이것이 실제로 가난하고 정당화되지 않은 "원칙"이라고 생각한다 : 높은 수준의 모듈은 재사용 할 수있다.
Rogério

3
일부 객체에 대한 종속성 그래프를 고려하십시오. 개체에 DIP를 적용하십시오. 이제 모든 객체는 다른 객체의 구현에 독립적입니다. 단위 테스트는 이제 간단합니다. 재사용을위한 나중에 리팩토링이 가능합니다. 설계 변경에는 변경 범위가 매우 제한되어 있습니다. 디자인 문제는 연속되지 않습니다. 데이터 의존성 역전에 대해서는 AI 패턴 "칠판"을 참조하십시오. 소프트웨어를 이해하고 유지 보수하며 신뢰할 수있게하는 매우 강력한 도구입니다. 이 컨텍스트에서 종속성 삽입을 무시하십시오. 관련이 없습니다.
Tim Williscroft

6
클래스 B를 사용하는 클래스 A는 A가 B의 구현에 의존한다는 것을 의미하지는 않습니다. B의 공용 인터페이스에만 의존합니다. A와 B가 모두 종속되는 별도의 추상화를 추가한다는 것은 A가 더 이상 B의 공용 인터페이스에 대한 컴파일 타임 종속성을 갖지 않음을 의미합니다. 이러한 추가 추상화없이 장치 간의 격리를 쉽게 달성 할 수 있습니다. Java 및 .NET에는 모든 상황 (정적 메소드, 생성자 등)을 처리하는 특정 조롱 도구가 있습니다. DIP를 적용하면 소프트웨어가 더 복잡하고 유지 관리가 용이하지 않으며 테스트 할 수 없게됩니다.
Rogério

1
그렇다면 제어 역전이란 무엇입니까? 의존성 역전을 달성하는 방법?
user20358

2
@Rogerio, 아래 Derek Greer 응답을 참조하십시오. AFAIK, DIP는 A가 A가 필요로하는 것을 말하는 사람이 아니라 A가 A가 필요한 것을 요구하는 사람이라고 말합니다. 따라서 A가 필요로하는 인터페이스는 B가 아니라 A를 위해 제공되어야합니다.
ejaenv

145

C #의 민첩한 소프트웨어 개발, 원칙, 패턴 및 사례와 민첩한 원칙, 패턴 및 사례는 Dependency Inversion Principle의 원래 목표와 동기를 완전히 이해하는 데 가장 적합한 리소스입니다. "The Dependency Inversion Principle"이라는 기사도 좋은 자료이지만, 이전에 언급 된 책으로 들어가는 초안의 요약 버전이라는 사실 때문에 이 원칙을보다 일반적인 조언과 구별하기위한 핵심 인 패키지 및 인터페이스 소유권은 Design Patterns (Gamma, et al.) 책에서 찾을 수있는 "구현이 아닌 인터페이스로의 프로그래밍"에 대한 조언입니다.

요약을 제공하기 위해 Dependency Inversion Principle은 주로 "상위 레벨"구성 요소가 "상위 레벨"구성 요소가 소유 한 인터페이스에 종속되도록 "상위 레벨"구성 요소에서 "하위 레벨"구성 요소로의 기존 종속성 방향을 반전시키는 것에 관한 것 입니다. . (참고 : 여기서 "높은 수준"구성 요소는 계층 구조 내에서 개념적 위치 일 필요는 없지만 외부 종속성 / 서비스가 필요한 구성 요소를 나타냅니다. 그렇게하면 이론적으로 구성 요소에서 이동 하는 것처럼 커플 링이 크게 줄어들지 는 않습니다. 이론적으로 더 가치가있는 구성 요소에는 덜 가치가 있습니다.

이것은 외부 의존성이 컴포넌트의 소비자에 의해 구현이 제공되어야하는 인터페이스의 관점에서 표현되는 컴포넌트를 설계함으로써 달성된다. 다시 말해, 정의 된 인터페이스는 컴포넌트 사용 방법이 아니라 컴포넌트에 필요한 것을 표현합니다 (예 : "IDoSomething"이 아닌 "INeedSomething").

Dependency Inversion Principle에서 언급하지 않는 것은 인터페이스를 사용하여 종속성을 추상화하는 간단한 방법입니다 (예 : MyService → [ILogger ⇐ Logger]). 이렇게하면 종속성의 특정 구현 세부 사항에서 구성 요소가 분리되지만 소비자와 종속성 간의 관계는 반전되지 않습니다 (예 : [MyService → IMyServiceLogger] ⇐ Logger).

Dependency Inversion Principle의 중요성은 기능의 일부 (로깅, 유효성 검사 등)에 외부 종속성에 의존하는 소프트웨어 구성 요소를 재사용 할 수 있다는 단일 목표로 나눌 수 있습니다.

이 일반적인 재사용 목표 내에서 두 가지 하위 유형의 재사용을 설명 할 수 있습니다.

  1. 하위 종속성 구현으로 여러 응용 프로그램 내에서 소프트웨어 구성 요소 사용 (예 : DI 컨테이너를 개발하고 로깅을 제공하려고하지만 컨테이너를 사용하는 모든 사람이 특정 로거에 컨테이너를 연결하지는 않음) 선택한 로깅 라이브러리를 사용하십시오).

  2. 진화하는 상황에서 소프트웨어 구성 요소 사용 (예 : 구현 세부 사항이 발전하는 여러 버전의 응용 프로그램에서 동일하게 유지되는 비즈니스 논리 구성 요소를 개발했습니다).

인프라 라이브러리와 같이 여러 응용 프로그램에서 구성 요소를 재사용하는 첫 번째 경우 목표는 이러한 종속성에 의존하기 위해서는 소비자가 자신의 라이브러리의 하위 종속성에 연결하지 않고 소비자에게 핵심 인프라를 제공하는 것입니다. 소비자들도 같은 의존성을 요구해야합니다. 라이브러리 소비자가 동일한 인프라 요구 사항 (예 : NLog와 log4net)에 대해 다른 라이브러리를 사용하기로 선택하거나 이전 버전과 호환되지 않는 필수 라이브러리의 이후 버전을 사용하기로 선택한 경우 문제가 될 수 있습니다. 도서관에서 필요합니다.

비즈니스 논리 구성 요소 (예 : "상위 레벨 구성 요소")를 재사용하는 두 번째 경우의 목표는 응용 프로그램의 핵심 도메인 구현을 구현 세부 사항의 변경 요구 (예 : 지속성 라이브러리, 메시징 라이브러리 변경 / 업그레이드)에서 분리하는 것입니다. , 암호화 전략 등). 이상적으로 응용 프로그램의 구현 세부 사항을 변경해도 응용 프로그램의 비즈니스 논리를 캡슐화하는 구성 요소가 손상되지 않아야합니다.

참고 : 일부는이 두 번째 사례를 실제 재사용으로 설명하는 데 반대 할 수 있습니다. 단일 진화하는 응용 프로그램 내에서 사용되는 비즈니스 논리 구성 요소와 같은 구성 요소는 단일 용도 일뿐입니다. 그러나 여기서 아이디어는 응용 프로그램의 구현 세부 사항을 변경할 때마다 새로운 컨텍스트와 다른 사용 사례를 렌더링하지만 궁극적 목표는 격리와 이식성으로 구분 될 수 있다는 것입니다.

이 두 번째 사례에서 Dependency Inversion Principle을 따르는 것이 약간의 이점을 제공 할 수 있지만 Java 및 C #과 같은 현대 언어에 적용되는 가치는 크게 관련이없는 수준으로 줄어든다는 점에 유의해야합니다. 앞에서 설명한 것처럼 DIP는 구현 세부 정보를 별도의 패키지로 완전히 분리합니다. 그러나 진화하는 애플리케이션의 경우, 비즈니스 도메인 측면에서 정의 된 인터페이스를 사용하면 구현 세부 사항 구성 요소의 변경 요구로 인해 더 높은 레벨의 구성 요소를 수정해야 할 필요가 없습니다. . 이 원칙의이 부분은 새로운 언어와 관련이없는 원칙이 체계화 될 때 (즉, C ++) 해당 언어와 관련된 측면을 반영합니다. 그것은 말했다

인터페이스의 간단한 사용, 종속성 주입 및 분리 된 인터페이스 패턴과 관련된이 원칙에 대한 자세한 설명은 여기를 참조하십시오 . 또한이 원칙이 JavaScript와 같은 동적 유형 언어와 어떤 관련이 있는지에 대한 설명은 여기를 참조하십시오 .


17
감사. 이제 내 대답이 요점을 놓치는 방법을 봅니다. 간의 차이 이면 MyService → [하는 ILogger ⇐ 로거][이면 MyService → IMyServiceLogger] ⇐ 로거는 미묘하지만 중요하다.
Patrick McElhaney

2
같은 줄에서 여기에 잘 설명되어 있습니다 : lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
사람들이 의존성 주입의 표준 사용 사례로 로거 사용을 중단하기를 원했던 방법. 특히 거의 안티 패턴의 log4net과 관련하여. 그 옆으로, kickass 설명!
캐스퍼 레온 닐슨

4
@ 캐스퍼 레온 닐슨 (Casper Leon Nielsen) – DIP는 DI와 아무 관련이 없습니다. 동의어 나 동등한 개념이 아닙니다.
TSmith

4
@ VF1 요약 단락에 언급 된 바와 같이, 의존성 역전 원리의 중요성은 주로 재사용됩니다. John이 로깅 라이브러리에 대한 외부 종속성이있는 라이브러리를 해제하고 Sam이 John의 라이브러리를 사용하려는 경우 Sam은 임시 로깅 종속성을 사용합니다. Sam은 로깅 라이브러리 John을 선택하지 않으면 응용 프로그램을 배포 할 수 없습니다. 그러나 John이 DIP를 따르는 경우 Sam은 어댑터를 제공하고 선택한 로깅 라이브러리를 자유롭게 사용할 수 있습니다. DIP는 편의성이 아니라 결합입니다.
Derek Greer

14

소프트웨어 응용 프로그램을 설계 할 때는 기본 및 기본 작업 (디스크 액세스, 네트워크 프로토콜 등)을 구현하는 하위 수준 클래스와 복잡한 논리 (비즈니스 흐름 등)를 캡슐화하는 클래스를 고려할 수 있습니다.

마지막 클래스는 저급 클래스에 의존합니다. 이러한 구조를 구현하는 자연스러운 방법은 저수준 클래스를 작성하고 일단 복잡한 클래스를 작성하는 것입니다. 높은 수준의 클래스는 다른 클래스로 정의되므로 논리적 인 방법입니다. 그러나 이것은 유연한 디자인이 아닙니다. 저수준 클래스를 교체해야하는 경우 어떻게됩니까?

종속성 반전 원리는 다음과 같습니다.

  • 고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.
  • 추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 의존해야합니다.

이 원칙은 소프트웨어의 높은 수준의 모듈이 낮은 수준의 모듈에 의존해야한다는 기존의 개념을 "반전"시키려고합니다. 여기서 상위 레벨 모듈은 하위 레벨 모듈에 의해 구현되는 추상화 (예 : 인터페이스의 방법 결정)를 소유합니다. 따라서 하위 모듈을 상위 모듈에 의존하게합니다.


11

종속성 반전이 잘 적용되면 애플리케이션의 전체 아키텍처 수준에서 유연성과 안정성이 제공됩니다. 이를 통해 애플리케이션이보다 안전하고 안정적으로 발전 할 수 있습니다.

전통적인 계층 구조

일반적으로 계층화 된 아키텍처 UI는 비즈니스 계층에 의존하고 이는 데이터 액세스 계층에 의존합니다.

계층, 패키지 또는 라이브러리를 이해해야합니다. 코드가 어떻게 작동하는지 봅시다.

데이터 액세스 계층을위한 라이브러리 또는 패키지가 있습니다.

// DataAccessLayer.dll
public class ProductDAO {

}

그리고 데이터 액세스 계층에 의존하는 다른 라이브러리 또는 패키지 계층 비즈니스 로직.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

종속성 반전을 사용하는 계층화 된 아키텍처

종속성 반전은 다음을 나타냅니다.

고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.

추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 따라 달라집니다.

고수준 모듈과 저수준은 무엇입니까? 라이브러리 또는 패키지와 같은 모듈, 상위 수준 모듈은 전통적으로 의존성이 있고 의존도가 낮은 수준입니다.

다시 말해, 모듈 상위 레벨은 조치가 호출되는 위치이고 하위 레벨은 조치가 수행되는 위치입니다.

이 원칙을 근거로 합리적인 결론은 결단 사이에 의존성이 없어야하지만 추상화에 의존해야한다는 것입니다. 그러나 우리가 취하는 접근법에 따르면 투자 의존도를 잘못 적용 할 수 있지만 추상화입니다.

다음과 같이 코드를 수정했다고 상상해보십시오.

추상화를 정의하는 데이터 액세스 계층을위한 라이브러리 또는 패키지가 있습니다.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

그리고 데이터 액세스 계층에 의존하는 다른 라이브러리 또는 패키지 계층 비즈니스 로직.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

우리는 비즈니스와 데이터 액세스 간의 추상화 종속성에 의존하지만 여전히 동일합니다.

종속성 반전을 얻으려면 지속성 인터페이스가이 높은 수준의 논리 또는 도메인이 있고 낮은 수준의 모듈이 아닌 모듈 또는 패키지에서 정의되어야합니다.

먼저 도메인 계층이 무엇인지 정의하고 통신의 추상화는 지속성으로 정의됩니다.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

지속성 계층이 도메인에 의존 한 후 종속성이 정의되면 지금 반전시킵니다.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(출처 : xurxodev.com )

원리 심화

개념을 잘 동화시키고 목적과 이점을 심화시키는 것이 중요합니다. 기계적으로 머물면서 전형적인 사례 저장소를 배우면 의존성 원칙을 적용 할 수있는 곳을 식별 할 수 없습니다.

그러나 왜 의존성을 뒤집는가? 구체적인 예를 넘어서는 주요 목표는 무엇입니까?

일반적으로 덜 안정적인 것들에 의존하지 않는 가장 안정적인 것들이 더 자주 변경 될 수 있습니다.

데이터베이스 또는 기술이 지속성과 통신하기 위해 설계된 도메인 논리 나 작업과 동일한 데이터베이스에 액세스하는 지속성 유형을 변경하는 것이 더 쉽습니다. 이 때문에이 변경이 발생하면 지속성을 변경하기가 쉽기 때문에 종속성이 반대로됩니다. 이런 식으로 도메인을 변경할 필요가 없습니다. 도메인 계층은 무엇보다 가장 안정적이므로 어떤 것에 의존해서는 안됩니다.

그러나이 저장소 예제 만있는 것은 아닙니다. 이 원칙이 적용되는 많은 시나리오가 있으며이 원칙을 기반으로하는 아키텍처가 있습니다.

아키텍처

종속성 반전이 정의의 핵심 인 아키텍처가 있습니다. 모든 도메인에서 가장 중요하며 도메인과 나머지 패키지 또는 라이브러리 간의 통신 프로토콜이 정의되었음을 나타내는 추상화입니다.

깨끗한 건축

에서 청소 아키텍처 도메인은 중앙에 위치하고 의존성을 나타내는 화살표 방향으로 보면, 가장 중요하고 안정적인 층이 무엇인지 분명합니다. 외부 레이어는 불안정한 도구로 간주되므로 의존하지 마십시오.


(출처 : 8thlight.com )

6 각형 아키텍처

도메인이 중앙 부분에 위치하고 포트가 도미노 외부에서 통신의 추상화 인 6 각형 아키텍처와 동일한 방식으로 발생합니다. 여기서도 도메인이 가장 안정적이며 전통적인 의존성이 반전된다는 것이 분명합니다.


무슨 말을해야할지 모르겠지만,이 문장은 일관되지 않습니다 . 아마도 고치고 싶습니까?
Mark Amery

9

나에게, 공식 기사에 설명 된 종속성 반전 원리 은 본질적으로 재사용 성이 떨어지는 모듈의 재사용 가능성을 높이고 C ++ 언어의 문제를 해결하는 방법으로 잘못 안내 된 시도입니다.

C ++의 문제는 헤더 파일에 일반적으로 개인 필드 및 메서드 선언이 포함되어 있다는 것입니다. 따라서 상위 레벨 C ++ 모듈에 하위 레벨 모듈의 헤더 파일이 포함 된 경우 해당 모듈의 실제 구현 세부 사항에 따라 다릅니다 . 그리고 그것은 분명히 좋은 것이 아닙니다. 그러나 이것은 오늘날 일반적으로 사용되는보다 현대적인 언어에서는 문제가되지 않습니다.

높은 수준의 모듈은 기본적으로 낮은 수준의 모듈보다 재사용 성이 떨어집니다. 전자는 일반적으로 후자보다 더 많은 응용 프로그램 / 컨텍스트입니다. 예를 들어, UI 화면을 구현하는 구성 요소는 응용 프로그램에 따라 최상위 수준이며 매우 완전합니다. 다른 응용 프로그램에서 이러한 구성 요소를 재사용하려고하면 생산성이 떨어지고 과도한 엔지니어링 만 가능합니다.

따라서 컴포넌트 B에 의존하는 (A에 의존하지 않는) 컴포넌트 A의 동일한 레벨에서 별도의 추상화를 생성하는 것은 컴포넌트 A가 실제로 다른 애플리케이션이나 컨텍스트에서 재사용하는 데 유용 할 경우에만 수행 할 수 있습니다. 그렇지 않다면 DIP를 적용하는 것이 좋지 않습니다.


높은 수준의 추상화가 해당 응용 프로그램의 컨텍스트에서만 유용하더라도 실제 구현을 테스트 용 스텁으로 바꾸거나 웹에서 일반적으로 사용 가능한 응용 프로그램에 명령 줄 인터페이스를 제공하려는 경우 가치가있을 수 있습니다.
Przemek Pokrywka

3
DIP는 여러 언어에서 중요하며 C ++ 자체와는 아무런 관련이 없습니다. 고급 코드가 응용 프로그램을 떠나지 않더라도 DIP를 사용하면 하위 수준 코드를 추가하거나 변경할 때 코드 변경 내용을 포함 할 수 있습니다. 이는 유지 보수 비용과 의도하지 않은 변경의 결과를 모두 줄입니다. DIP는 더 높은 수준의 개념입니다. 이해하지 못하면 더 인터넷 검색을해야합니다.
Dirk Bester

3
C ++에 대해 내가 말한 것을 오해했다고 생각합니다. DIP 의 동기 일뿐입니다 . 의심 할 여지없이 그보다 더 일반적입니다. DIP에 대한 공식 기사를 통해 높은 수준의 모듈의 재사용을 지원하는 핵심 동기가 저수준 모듈의 변경에 영향을받지 않음을 분명히 알 수 있습니다. 재사용 할 필요가 없으면 과잉과 과잉 엔지니어링 가능성이 높습니다. (읽었습니까? 또한 C ++ 문제에 대해서도 이야기합니다.)
Rogério

1
DIP의 역 적용을 간과하지 않습니까? 즉, 상위 레벨 모듈은 기본적으로 애플리케이션을 구현합니다. 주요 관심사는 재사용하지 않고 하위 레벨 모듈의 구현에 덜 의존하여 미래의 변화에 ​​보조를 맞추기 위해 업데이트하면 응용 프로그램이 시간과 새로운 기술의 폐허로부터 격리되도록하는 것입니다. WindowsOS를 쉽게 교체 할 수있는 것은 아니며 FAT / HDD의 구현 세부 사항에 대한 WindowsOS의 의존도를 낮추어 새로운 NTFS / SSD를 거의 영향을 미치지 않고 WindowsOS에 연결할 수 있습니다.
knockNrod

1
UI 화면은 분명히 최상위 모듈이 아니거나 응용 프로그램에 사용해서는 안됩니다. 최상위 레벨 모듈은 비즈니스 규칙이 포함 된 모듈입니다. 최고 수준의 모듈은 재사용 가능성으로 정의되지 않습니다. 이 기사에서 Bob 아저씨의 간단한 설명을 확인하십시오 : blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

기본적으로 그것은 말합니다 :

클래스는 특정 세부 사항 (구현)이 아닌 추상화 (예 : 인터페이스, 추상 클래스)에 의존해야합니다.


그렇게 간단 할 수 있습니까? DerekGreer가 언급 한 것처럼 긴 기사와 책이 있습니까? 나는 실제로 간단한 대답을 찾고 있었지만 그것이 그렇게 단순하다면 믿을 수 없었습니다 : D
Darius.V

1
@ darius-v "사용 추상화"라는 다른 버전이 아닙니다. 누가 인터페이스를 소유하는지에 관한 것입니다. 교장은 클라이언트 (상위 레벨 구성 요소)가 인터페이스를 정의하고 하위 레벨 구성 요소가이를 구현해야한다고 말합니다.
boran

6

좋은 답변과 좋은 예는 이미 다른 사람들이 제공 한 것입니다.

DIP 이유 가 중요한 는 OO 원칙 "느슨하게 결합 된 디자인"을 보장하기 때문입니다.

소프트웨어의 개체는 하위 개체에 따라 일부 개체가 최상위 개체 인 계층으로 들어가서는 안됩니다. 낮은 수준의 개체를 변경하면 최상위 개체로 리플 스루되어 소프트웨어가 변경되기 매우 취약합니다.

'최상위'개체가 매우 안정적이며 변경에 취약하지 않기를 원하므로 종속성을 반전시켜야합니다.


6
Java 또는 .NET 코드의 경우 DIP가 정확히 어떻게합니까? 이러한 언어에서 C ++과 달리 저수준 모듈의 구현 변경은이를 사용하는 고수준 모듈의 변경을 요구하지 않습니다. 공용 인터페이스의 변경 사항 만 리플 스루되지만 상위 수준에서 정의 된 추상화도 변경되어야합니다.
Rogério

5

Dependency Inversion Principle을 명시하는 훨씬 명확한 방법은 다음과 같습니다.

복잡한 비즈니스 로직을 캡슐화하는 모듈은 비즈니스 로직을 캡슐화하는 다른 모듈에 직접 의존해서는 안됩니다. 대신, 단순한 데이터에 대한 인터페이스에만 의존해야합니다.

즉, Logic사람들이 일반적으로하는 것처럼 수업을 구현하는 대신 :

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

당신은 다음과 같은 일을해야합니다 :

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

DataDataFromDependency같은 모듈에 살아야 Logic하지와 함께,Dependency .

왜 이렇게합니까?

  1. 두 개의 비즈니스 로직 모듈이 분리되었습니다. Dependency변경 하면 변경할 필요가 없습니다Logic .
  2. Logic수행하는 작업을 이해하는 것이 훨씬 간단한 작업입니다. ADT처럼 보이는 작업에서만 작동합니다.
  3. Logic이제 더 쉽게 테스트 할 수 있습니다. 이제 Data가짜 데이터로 직접 인스턴스화 하여 전달할 수 있습니다. 모의 또는 복잡한 테스트 스캐 폴딩이 필요하지 않습니다.

나는 이것이 옳다고 생각하지 않습니다. 경우 DataFromDependency이는 직접 참조, Dependency같이 동일한 모듈에 Logic다음, Logic모듈은 여전히 직접에 따라 Dependency컴파일시에 모듈. 당 원칙의 삼촌 밥의 설명 , 그것을 피하는 것은 DIP의 전체 지점입니다. 오히려, DIP를 수행하기 위해, Data동일 모듈에 있어야 Logic하지만, DataFromDependency같은 모듈에 있어야한다 Dependency.
마크 애 머리

3

제어 역전IoC ( )는 개체가 프레임 워크에 의존성을 요구하지 않고 외부 프레임 워크가 의존성을 전달하는 디자인 패턴입니다.

기존 조회를 사용하는 의사 코드 예 :

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

IoC를 사용하는 비슷한 코드 :

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

IoC의 이점은 다음과 같습니다.

  • 중앙 프레임 워크에 의존하지 않으므로 원하는 경우 변경할 수 있습니다.
  • 인젝션은 인터페이스를 사용하여 주입으로 작성되므로 종속성을 모의 버전으로 대체하는 단위 테스트를 작성하기 쉽습니다.
  • 코드를 분리합니다.

종속성 반전 원리와 제어 반전은 같은 것입니까?
Peter Mortensen

3
DIP의 "반전"입니다 하지 제어의 반전과 동일. 첫 번째는 컴파일 타임 종속성에 관한 것이고, 두 번째는 런타임에 메소드 간의 제어 흐름에 관한 것입니다.
Rogério

IoC 버전에 무언가 빠진 것 같습니다. 데이터베이스 개체는 어떻게 정의되며 어디에서 왔습니까? 나는 이것이 단지 거대한 신 의존성의 최상위에있는 모든 의존성을 지정 지연하지 않는 방법을 이해하기 위해 노력하고있어
리처드 설렘

1
@ Rogério가 이미 말했듯이 DIP는 DI / IoC가 아닙니다. 이 답변은 잘못되었습니다. IMO
zeraDev

1

의존성 역전 지점은 재사용 가능한 소프트웨어를 만드는 것입니다.

아이디어는 서로 의존하는 두 개의 코드 대신 추상적 인 인터페이스에 의존한다는 것입니다. 그런 다음 다른 것없이 두 조각을 재사용 할 수 있습니다.

가장 일반적인 방법은 Spring in Java와 같은 IoC (Inversion of Control) 컨테이너를 사용하는 것입니다. 이 모델에서 객체의 속성은 객체가 나가고 종속성을 찾는 대신 XML 구성을 통해 설정됩니다.

이 의사 코드를 상상해보십시오 ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass는 Service 클래스와 ServiceLocator 클래스에 직접 의존합니다. 다른 응용 프로그램에서 사용하려면 두 가지가 모두 필요합니다. 이제 이것을 상상해보십시오 ...

public class MyClass
{
  public IService myService;
}

이제 MyClass는 단일 인터페이스 인 IService 인터페이스를 사용합니다. 우리는 IoC 컨테이너가 실제로 그 변수의 값을 설정하도록했습니다.

이제 MyClass는 다른 두 클래스의 종속성을 가져 오지 않고도 다른 프로젝트에서 쉽게 재사용 할 수 있습니다.

더 좋은 점은 MyService의 종속성과 해당 종속성의 종속성을 끌 필요가 없다는 것입니다.


2
이 답변은 실제로 DIP가 아니라 다른 것입니다. 두 개의 코드는 서로 추상화되지 않은 추상화 된 인터페이스에 의존 할 수 있으며 여전히 의존성 역전 원칙을 따르지 않습니다.
Rogério

1

회사의 "고급"직원이 계획 실행에 대해 비용을 지불하고 이러한 계획이 많은 "저급"직원 계획의 총괄 실행에 의해 제공된다고 가정하면이를 말할 수 있습니다. 상위 직원의 계획 설명이 어떤 식 으로든 하위 직원의 특정 계획과 결합되는 경우 일반적으로 끔찍한 계획입니다.

고위 임원이 "배달 시간을 개선"할 계획이 있고 해운 회사 직원이 매일 아침 커피를 마시고 스트레칭을해야한다고 나타내는 경우, 해당 계획은 밀접하게 연계되어 있고 응집력이 낮습니다. 그러나 계획에서 특정 직원을 언급하지 않고 실제로 "작업을 수행 할 수있는 실체가 작동 할 준비가되어 있어야합니다"라는 계획이 필요한 경우 계획이 느슨하게 결합되어보다 응집력이 높아집니다. 계획이 겹치지 않고 쉽게 대체 될 수 있습니다. . 계약자 또는 로봇은 직원을 쉽게 대체 할 수 있으며 높은 수준의 계획은 변경되지 않습니다.

종속성 반전 원리에서 "높은 수준"은 "더 중요한"을 의미합니다.


1
이것은 상당히 좋은 비유입니다. DIC 하에서 상위 레벨 컴포넌트가 여전히 런타임 에 하위 레벨 컴포넌트에 의존하는 것처럼, 이와 유사하게 상위 레벨 계획의 실행은 하위 레벨 계획에 따라 다릅니다. 그러나 DIC에서와 마찬가지로 컴파일 타임 에 높은 수준의 계획에 의존하는 낮은 수준의 계획이므로, 낮은 수준의 계획의 형성은 높은 수준의 계획에 달려 있다는 것이 유추됩니다.
마크 애 머리

0

위의 답변에 좋은 설명이 있음을 알 수 있습니다. 그러나 간단한 예제로 쉬운 설명을 제공하고 싶습니다.

Dependency Inversion Principle은 프로그래머가 하드 코딩 된 종속성을 제거하여 응용 프로그램이 느슨하게 연결되고 확장 될 수 있도록합니다.

이것을 달성하는 방법 : 추상화를 통해

의존성 역전없이 :

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

위의 코드 스 니펫에서 주소 개체는 하드 코딩됩니다. 대신 의존성 역전을 사용하고 생성자 또는 setter 메소드를 통해 주소 객체를 주입 할 수 있다면. 보자

의존성 역전 :

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}


-1

하자 말 우리는 두 개의 클래스를 가지고 : EngineerProgrammer :

클래스 엔지니어는 아래와 같이 프로그래머 클래스에 의존합니다.

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

이 예제에서 클래스 Engineer는 클래스 에 의존합니다 Programmer. 변경해야 할 경우 어떻게됩니까?Programmer 어떻게됩니까?

분명히 나도 변경해야합니다 Engineer. (이 시점에서OCP 에서도 위반)

그렇다면이 엉망진창을 어떻게 정리해야합니까? 답은 실제로 추상화입니다. 추상화하여이 두 클래스 간의 종속성을 제거 할 수 있습니다. 예를 들어, InterfaceProgrammer 클래스를 만들 수 있으며 이제부터 사용하려는 모든 클래스는 Programmer 클래스 Programmer를 사용해야합니다.Interface 때문에 추상화의, 그리고 프로그래머 클래스를 변경하여, 우리가 그것을 사용 된 모든 클래스를 변경하지 않아도 우리 익숙한.

참고 : DependencyInjection우리가 할 데 도움이 될 수 있습니다 DIPSRP도.


-1

일반적으로 좋은 답변을 더하기 위해 좋은 연습과 나쁜 연습을 보여주기 위해 작은 샘플을 추가하고 싶습니다. 그리고 네, 나는 돌을 던지는 사람이 아닙니다!

콘솔 I / O를 통해 문자열을 base64 형식으로 변환 하는 작은 프로그램이 필요합니다 . 순진한 접근 방식은 다음과 같습니다.

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP는 기본적으로 상위 레벨 컴포넌트는 하위 레벨 구현에 의존해서는 안된다고 말합니다. 여기서 "레벨"은 Robert C. Martin ( "Clean Architecture")에 따르면 I / O와의 거리입니다. 그러나이 곤경에서 어떻게 벗어날 수 있습니까? 중앙 인코더가 인터페이스 구현 방식을 방해하지 않고 인터페이스에만 의존하게함으로써 간단히 :

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

GoodEncoderI / O 모드를 변경하기 위해 손대지 않아도됩니다. 클래스는 알고있는 I / O 인터페이스에 만족합니다. 의 낮은 수준의 구현 IReadable과는 IWriteable이제까지 그것을 귀찮게하지 않습니다.


이것이 의존성 반전이라고 생각하지 않습니다. 결국, 당신은 여기서 어떤 의존도를 뒤집지 않았습니다. 당신의 독자와 작가는 GoodEncoder두 번째 예 에 의존하지 않습니다 . DIP 예제를 만들려면 여기에서 추출한 인터페이스 "소유"에 대한 개념을 도입해야합니다. 특히 구현이 외부에있는 동안 GoodEncoder와 동일한 패키지에 배치해야합니다.
Mark Amery

-2

의존성 역전 원리 (DIP)는

i) 고수준 모듈은 저수준 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.

ii) 추상화는 세부 사항에 의존해서는 안됩니다. 세부 사항은 추상화에 의존해야합니다.

예:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

참고 : 클래스는 특정 세부 정보 (인터페이스 구현)가 아닌 인터페이스 또는 추상 클래스와 같은 추상화에 의존해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.