문제

종속성 역전 원칙은 무엇이며 왜 중요한가요?

도움이 되었습니까?

해결책

이 문서를 확인하세요: 종속성 반전 원칙.

기본적으로 다음과 같이 말합니다.

  • 높은 수준의 모듈은 낮은 수준의 모듈에 의존해서는 안 됩니다.둘 다 추상화에 의존해야 합니다.
  • 추상화는 결코 세부사항에 의존해서는 안 됩니다.세부 사항은 추상화에 따라 달라집니다.

왜 중요한지 간단히 말하면:변경은 위험하며 구현이 아닌 개념에 따라 호출 현장에서 변경의 필요성이 줄어듭니다.

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

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

다른 팁

Agile Software Development, 원칙, 패턴 및 사례 및 Agile 원칙, 패턴 및 사례 C# 책은 종속성 역전 원칙의 원래 목표와 동기를 완전히 이해하기 위한 최고의 리소스입니다."의존성 역전 원칙"이라는 기사도 좋은 자료이지만 이전에 언급한 책에 포함된 초안의 압축 버전이라는 사실로 인해 개념에 대한 몇 가지 중요한 논의가 빠져 있습니다. 패키지 및 인터페이스 소유권은 이 원칙을 Design Patterns(Gamma, et.알).

요약하자면 종속성 역전 원칙은 주로 다음과 같습니다. 반전 "낮은 수준" 구성 요소가 인터페이스에 종속되도록 "상위 수준" 구성 요소에서 "하위 수준" 구성 요소로의 기존 종속성 방향 소유 "상위 수준" 구성 요소에 의해.(메모:여기서 "상위 수준" 구성 요소는 외부 종속성/서비스가 필요한 구성 요소를 의미하며 계층 아키텍처 내에서 개념적 위치가 반드시 필요한 것은 아닙니다.) 그렇게 하면 결합이 발생하지 않습니다. 줄인 그 만큼 이동 이론적으로 덜 가치 있는 구성 요소부터 이론적으로 더 가치 있는 구성 요소까지.

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

종속성 역전 원칙이 언급하지 않는 것은 인터페이스(예:MyService → [ILogger ⇐ Logger]).이는 종속성의 특정 구현 세부 사항에서 구성 요소를 분리하지만 소비자와 종속성 간의 관계를 반전시키지는 않습니다(예:[MyService → IMyServiceLogger] ⇐ 로거.

종속성 역전 원칙의 중요성은 기능(로깅, 검증 등)의 일부에 대해 외부 종속성에 의존하는 소프트웨어 구성 요소를 재사용할 수 있다는 단일 목표로 압축될 수 있습니다.

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

  1. 하위 종속성 구현이 포함된 여러 애플리케이션 내에서 소프트웨어 구성요소 사용(예:DI 컨테이너를 개발했고 로깅을 제공하고 싶지만 컨테이너를 사용하는 모든 사람이 선택한 로깅 라이브러리도 사용해야 하는 것처럼 컨테이너를 특정 로거에 연결하고 싶지 않습니다.

  2. 진화하는 맥락에서 소프트웨어 구성 요소 사용(예:구현 세부 사항이 발전하는 여러 버전의 애플리케이션에서 동일하게 유지되는 비즈니스 논리 구성 요소를 개발했습니다.

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

비즈니스 로직 구성 요소를 재사용하는 두 번째 경우(예:"상위 수준 구성 요소")의 목표는 구현 세부 사항의 변화하는 요구(예:지속성 라이브러리, 메시징 라이브러리, 암호화 전략 등 변경/업그레이드).이상적으로는 애플리케이션의 구현 세부 사항을 변경해도 애플리케이션의 비즈니스 로직을 캡슐화하는 구성 요소가 손상되어서는 안 됩니다.

메모:어떤 사람들은 이 두 번째 사례를 실제 재사용으로 설명하는 데 반대할 수 있습니다. 진화하는 단일 애플리케이션 내에서 사용되는 비즈니스 논리 구성 요소와 같은 구성 요소는 단일 사용만을 나타낸다고 추론할 수 있습니다.그러나 여기서의 아이디어는 애플리케이션의 구현 세부 사항에 대한 각 변경이 새로운 컨텍스트를 렌더링하므로 다른 사용 사례를 렌더링한다는 것입니다. 하지만 궁극적인 목표는 격리와 격리로 구별될 수 있습니다.이식성.

이 두 번째 경우에서 종속성 역전 원칙을 따르는 것은 어느 정도 이점을 제공할 수 있지만 Java 및 C#과 같은 현대 언어에 적용되는 가치는 아마도 관련성이 없을 정도로 훨씬 감소한다는 점에 유의해야 합니다.앞에서 설명한 것처럼 DIP에는 구현 세부 사항을 별도의 패키지로 완전히 분리하는 작업이 포함됩니다.그러나 진화하는 애플리케이션의 경우 비즈니스 도메인 측면에서 정의된 인터페이스를 단순히 활용하면 구현 세부 사항이 궁극적으로 동일한 패키지 내에 있더라도 구현 세부 사항 구성 요소의 요구 사항 변화로 인해 상위 수준 구성 요소를 수정해야 하는 필요성을 방지할 수 있습니다.원칙의 이 부분은 원칙이 성문화되었을 때 고려된 언어와 관련된 측면을 반영합니다(예:C++) 최신 언어와 관련이 없습니다.즉, 종속성 역전 원칙의 중요성은 주로 재사용 가능한 소프트웨어 구성 요소/라이브러리의 개발에 있습니다.

인터페이스의 간단한 사용, 종속성 주입 및 분리된 인터페이스 패턴과 관련된 이 원칙에 대한 더 자세한 설명을 찾을 수 있습니다. 여기.또한 이 원칙이 JavaScript와 같은 동적 유형 언어와 어떻게 관련되는지에 대한 논의를 찾을 수 있습니다. 여기.

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

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

종속성 역전 원칙은 다음과 같이 명시합니다.

  • 높은 수준의 모듈은 낮은 수준의 모듈에 의존해서는 안 됩니다.둘 다 추상화에 의존해야 합니다.
  • 추상화는 세부사항에 의존해서는 안 됩니다.세부 사항은 추상화에 따라 달라집니다.

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

나에게는 종속성 역전 원칙이 설명되어 있습니다. 공식 기사, 본질적으로 재사용 가능성이 낮은 모듈의 재사용성을 높이려는 잘못된 시도이자 C++ 언어의 문제를 해결하는 방법입니다.

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

상위 수준 모듈은 일반적으로 하위 수준 모듈보다 애플리케이션/컨텍스트에 더 구체적이기 때문에 본질적으로 하위 수준 모듈보다 재사용성이 낮습니다.예를 들어, UI 화면을 구현하는 구성 요소는 가장 높은 수준에 속하며 애플리케이션에 매우 (완전히?) 구체적입니다.이러한 구성요소를 다른 애플리케이션에서 재사용하려는 시도는 비생산적이며 과도한 엔지니어링으로 이어질 수 있습니다.

따라서 구성 요소 B(A에 종속되지 않음)에 의존하는 구성 요소 A의 동일한 수준에서 별도의 추상화를 생성하는 것은 구성 요소 A가 실제로 다른 응용 프로그램이나 컨텍스트에서 재사용하는 데 유용할 경우에만 수행될 수 있습니다.그렇지 않다면 DIP를 적용하는 것은 나쁜 디자인이 될 것입니다.

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

전통적인 계층형 아키텍처

전통적으로 계층화된 아키텍처 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

원리 심화

개념을 잘 흡수하여 목적과 이점을 심화시키는 것이 중요합니다.우리가 기계적으로 머물면서 전형적인 사례 저장소를 배운다면 의존성의 원칙을 어디에 적용할 수 있는지 식별할 수 없을 것입니다.

그런데 왜 종속성을 반전시키는 걸까요?구체적인 사례를 넘어서 주요 목적은 무엇입니까?

이처럼 일반적으로 덜 안정적인 것에 의존하지 않고 가장 안정적인 것을 더 자주 변경할 수 있습니다.

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

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

아키텍처

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

클린 아키텍처

~ 안에 클린 아키텍처 도메인이 중앙에 위치하고 있으며 의존성을 나타내는 화살표 방향을 보면 가장 중요하고 안정적인 레이어가 무엇인지 분명하게 알 수 있습니다.외부 레이어는 불안정한 도구로 간주되므로 의존하지 마십시오.

육각형 건축

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

기본적으로 다음과 같이 말합니다.

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

종속성 반전 원칙을 설명하는 훨씬 더 명확한 방법은 다음과 같습니다.

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

즉, 클래스를 구현하는 대신 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
    }
}

Data 그리고 DataFromDependency 다음과 같은 모듈에 있어야 합니다. Logic, 아니 Dependency.

왜 이런 일을 하는가?

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

여기 다른 사람들이 이미 좋은 답변과 좋은 예를 제시했습니다.

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

소프트웨어의 개체는 하위 수준 개체에 따라 일부 개체가 최상위 개체인 계층 구조에 들어가서는 안 됩니다.낮은 수준 개체의 변경 사항은 최상위 개체로 전파되어 소프트웨어를 변경하기 매우 취약하게 만듭니다.

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

통제의 반전 (IoC)는 프레임워크에 종속성을 요청하는 대신 객체가 외부 프레임워크에 의해 종속성을 전달받는 디자인 패턴입니다.

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

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

IoC를 사용하는 유사한 코드:

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

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

  • 중앙 프레임 워크에 의존하지 않으므로 원하는 경우 변경할 수 있습니다.
  • 객체는 주입에 의해 생성되기 때문에 바람직하게는 인터페이스를 사용하여 의존성을 모의 버전으로 대체하는 단위 테스트를 쉽게 만들 수 있습니다.
  • 코드 분리.

종속성 반전의 핵심은 재사용 가능한 소프트웨어를 만드는 것입니다.

두 개의 코드가 서로 의존하는 대신 추상화된 인터페이스에 의존한다는 아이디어입니다.그런 다음 다른 부분 없이 두 부분 중 하나를 재사용할 수 있습니다.

이를 달성하는 가장 일반적인 방법은 Java의 Spring과 같은 IoC(제어 반전) 컨테이너를 사용하는 것입니다.이 모델에서는 개체가 나가서 종속성을 찾는 대신 개체의 속성이 XML 구성을 통해 설정됩니다.

이 의사코드를 상상해 보세요...

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

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

public class MyClass
{
  public IService myService;
}

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

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

더 좋은 점은 MyService의 종속성과 해당 종속성의 종속성을 드래그할 필요가 없다는 것입니다.글쎄, 당신은 아이디어를 얻습니다.

컨트롤 컨테이너의 반전과 종속성 주입 패턴 마틴 파울러(Martin Fowler)의 책도 잘 읽어보세요.나는 찾았다 헤드 퍼스트 디자인 패턴 DI와 기타 패턴을 처음으로 배우기 위한 멋진 책입니다.

종속성 반전:구체화가 아닌 추상화에 의존합니다.

제어 반전:메인과 추상화, 그리고 메인이 시스템의 접착제 역할을 하는 방법.

DIP and IoC

이에 대해 이야기하는 좋은 게시물은 다음과 같습니다.

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

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

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

종속성 역전 원칙(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();
        }
    }

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top