it-swarm-ko.tech

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

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

168
Phillip Wells

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

기본적으로 말합니다 :

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

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

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

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

102
Carl Seleborg

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

요약을 제공하기 위해 종속성 반전 원리는 주로 "더 높은 수준"구성 요소에서 "더 낮은 수준"구성 요소로의 "더 낮은 수준"구성 요소에 대한 일반적인 종속성 방향 reversing "상위 레벨"구성 요소에 의해 own 인터페이스에 의존합니다. (참고 : 여기서 "높은 수준"구성 요소는 계층 구조 내에서 개념적 위치 일 필요는 없지만 외부 종속성/서비스가 필요한 구성 요소를 나타냅니다. 그렇게 할 때 커플 링이 reduced 이론적으로 더 가치있는 구성 요소에 이론적으로 덜 가치있는 구성 요소로부터 shifted 만큼.

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

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

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

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

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

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

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

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

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

이 두 번째 경우에 의존성 역전 원칙을 따르는 것이 약간의 이점을 제공 할 수 있지만, Java 및 C #과 같은 현대 언어에 적용되는 값은 아마도 앞에서 언급했듯이 DIP는 구현 세부 정보를 별도의 패키지로 완전히 분리하는 것을 포함하지만, 진화하는 응용 프로그램의 경우 비즈니스 도메인 측면에서 정의 된 인터페이스를 사용하면 더 높은 수준의 구성 요소를 수정할 필요가 없습니다. 구현 세부 사항이 궁극적으로 동일한 패키지 내에있는 경우에도 구현 세부 사항 구성 요소의 요구 변경 원칙의이 부분은 원칙이 코드화 될 때 (예 : C++) 언어와 관련된 측면을 반영합니다. 즉, Dependency Inversion Principle의 중요성은 주로 재사용 가능한 소프트웨어 구성 요소/라이브러리의 개발에 있습니다.

인터페이스의 간단한 사용, 종속성 주입 및 분리 된 인터페이스 패턴과 관련된이 원칙에 대한 자세한 설명은 here 를 참조하십시오. 또한 원리가 JavaScript와 같은 동적 형식 언어와 어떻게 관련되는지에 대한 설명은 foudn here 가 될 수 있습니다.

137
Derek Greer

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

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

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

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

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

11
nikhil.singhal

저에게 공식 기사 에 설명 된 것처럼 Dependency Inversion Principle은 본질적으로 재사용 성이 떨어지는 모듈의 재사용 가능성을 높이려는 잘못된 시도이며 실제로 문제를 해결하는 방법입니다. C++ 언어.

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

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

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

10
Rogério

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

전통적인 계층 구조

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

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

// 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;
}

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

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

// Domain.dll
public interface IProductRepository;

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

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

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

원리 심화

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

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

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

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

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

아키텍처

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

깨끗한 건축

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

6 각형 아키텍처

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

9
xurxodev

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

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

7
martin.ra

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
    }
}

DataDataFromDependencyLogic이 아닌 Dependency과 동일한 모듈에 있어야합니다.

왜 이렇게합니까?

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

좋은 답변과 좋은 예는 이미 다른 사람들에 의해 제공되었습니다.

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

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

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

5
Hace

Inversion of control (IoC)는 개체가 프레임 워크를 요구하지 않고 외부 프레임 워크에 의해 오브젝트가 전달되는 디자인 패턴입니다.

전통적인 조회를 사용하는 의사 코드 예 :

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

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

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

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

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

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

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

가장 일반적인 방법은 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의 종속성과 해당 종속성의 종속성을 끌 필요가 없으며 아이디어를 얻는 것입니다.

1
Marc Hughes

나는 훨씬 더 나은 (더 직관적 인) 예를 가지고 있다고 생각합니다.

  • 직원 및 연락처 관리 (2 개의 화면)가있는 시스템 (webapp)을 상상해보십시오.
  • 그것들은 정확히 관련이 없으므로 각각의 모듈/폴더에서 각각 원합니다.

따라서 직원 관리 모듈 연락처 관리 모듈 모두에 대해 알아야 할 "주요"진입 점이 있으며 링크를 제공해야합니다. 즉, 메인 모듈은이 두 가지에 의존합니다. 즉, 컨트롤러는 공유, 탐색에서 렌더링되어야하는 컨트롤러, 경로 및 링크에 대해 알고 있습니다.

Node.js 예제

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

또한 이것이 완전히 괜찮은 경우 (단순한 경우가 있습니다)가 있습니다.


따라서 시간이 지남에 따라 새 모듈을 추가하는 것이 쉽지 않은 시점에 도달합니다. api, navigation, permissions를 등록해야합니다.이 main.js는 점점 커집니다.

그리고 그것은 의존성 역전이 일어나는 곳입니다. 주 모듈이 다른 모든 모듈에 종속되는 대신 "핵심"을 도입하고 모든 모듈을 자체적으로 등록합니다.

따라서이 경우 많은 서비스 (라우트, 탐색, 권한)에 제출 할 수있는 기본 ApplicationModule에 대한 개념을 가지고 있으며 기본 모듈은 간단하게 유지할 수 있습니다 (모듈을 가져 와서 설치하십시오)

다시 말해, 플러그 가능한 아키텍처를 만드는 것입니다. 이것은 추가 작업 및 코드이므로 쓰기/읽기 및 유지 관리해야하므로 이러한 종류의 냄새가 나는 경우에는 미리하지 말아야합니다.

특히 흥미로운 것은 플러그인, 심지어 퍼시스턴스 레이어까지 만들 수 있다는 것입니다. 많은 퍼시스턴스 구현을 지원해야 할 경우에는 가치가 있지만 일반적으로 그렇지 않습니다. 육각형 구조의 이미지에 대한 다른 답변을 참조하십시오. 일러스트에 적합합니다. 핵심이 있으며 다른 모든 것은 본질적으로 플러그인입니다.

0
Kamil Tomšík

의존성 역전 : concretion이 아닌 추상화에 의존합니다.

제어 역전 : 메인 대 추상화, 메인이 시스템의 접착제 인 방법.

DIP and IoC

이것에 대해 좋은 소식이 있습니다.

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

컨트롤 컨테이너의 반전과 의존성 주입 패턴 Martin Fowler의 글도 잘 읽었습니다. 나는 DI와 다른 패턴을 배우기위한 첫 번째 방법으로 Head First Design Patterns 멋진 책을 발견했습니다.

0
Chris Canal

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

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

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-lever 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 ( "청결한 아키텍처")에 따르면 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);
    }
}

I/O 모드를 변경하기 위해 GoodEncoder를 터치 할 필요는 없습니다. 클래스는 알고있는 I/O 인터페이스에 만족합니다. IReadableIWriteable의 저수준 구현은 결코 방해하지 않습니다.

0
John Silence

다른 답변 외에도 ....

예를 먼저 보여 드리겠습니다 ..

푸드 제너레이터에게 공급을 요청하는 호텔이 있습니다. 호텔은 음식 (닭고기)의 이름을 Food Generator에 제공하고 Generator는 요청한 음식을 호텔로 반환합니다. 그러나 호텔은 수용하고 제공하는 음식의 종류를 신경 쓰지 않습니다. 따라서 발전기는 음식에 "음식"이라는 레이블이있는 음식을 호텔에 공급합니다.

이 구현은 Java로되어 있음

공장 메소드가있는 FactoryClass. 음식 생성기

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


추상/인터페이스 클래스

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


닭은 음식을 구현합니다 (구체적인 종류)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


물고기는 음식을 구현합니다 (구체적인 종류)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


마침내

호텔

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

보시다시피 호텔은 치킨 오브젝트인지 물고기 오브젝트인지 알 수 없습니다. 그것은 그것이 음식물임을 알고 있습니다. 즉, 호텔은 음식 종류에 따라 다릅니다.

또한 피쉬 앤 치킨 클래스는 푸드 클래스를 구현하며 호텔과 직접 관련이 없습니다. 즉 닭고기와 생선도 식품 등급에 따라 다릅니다.

이는 상위 레벨 구성 요소 (호텔) 및 하위 레벨 구성 요소 (생선 및 닭고기)가 모두 추상화 (음식)에 따라 달라짐을 의미합니다.

이것을 의존성 반전이라고합니다.

0
Revolver

의존성 역전 원리 (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();
        }
    }

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

0
Rejwanul Reja