종속성 반전. 종속성 반전 원칙에 대한 비판적인 시각. 전통적인 계층형 아키텍처

사실 모든 원칙은 단단한그들은 서로 밀접하게 관련되어 있으며 주요 목표는 고품질의 확장 가능한 소프트웨어를 만드는 것입니다. 하지만 마지막 원칙은 단단한그들의 배경에 비해 정말 눈에 띕니다. 먼저, 이 원리의 공식화를 살펴보겠습니다. 그래서, 의존성 반전 원리 (종속성 역전 원리 - DIP): “추상화에 대한 의존성. 특정 항목에 의존하지 않습니다.”. 소프트웨어 개발 분야의 저명한 전문가인 로버트 마틴(Robert Martin)도 이 원칙을 강조합니다. 담그다그리고 단순히 다른 원칙을 따른 결과로 제시합니다. 단단한— 개방/폐쇄 원칙 및 Liskov 대체 원칙. 첫 번째는 새로운 변경 사항을 도입하기 위해 클래스를 수정해서는 안 된다고 말하고, 두 번째는 상속과 관련이 있으며 프로그램의 올바른 작동을 방해하지 않고 일부 기본 유형의 파생 유형을 안전하게 사용한다고 가정합니다. 로버트 마틴(Robert Martin)은 원래 이 원칙을 공식화했습니다. 다음과 같은 방법으로:

1). 높은 수준의 모듈은 낮은 수준의 모듈에 의존해서는 안 됩니다. 두 수준의 모듈은 추상화에 의존해야 합니다.

2). 추상화는 세부사항에 의존해서는 안 됩니다. 세부 사항은 추상화에 따라 달라져야 합니다.

즉, 클래스는 구체적인 구현이 아닌 추상화 측면에서 개발되어야 합니다. 그리고 원칙을 따른다면 OCP그리고 LSP, 그러면 이것이 바로 우리가 달성할 것입니다. 이제 수업으로 조금 돌아가 보겠습니다. 예를 들어, 우리는 수업을 고려했습니다. 음유 시인, 처음에는 클래스와 엄격하게 연결되어 있었습니다. 기타, 특정 악기를 나타냄:

공개 클래스 Bard ( 개인 기타 기타; 공개 Bard(기타 기타) ( this.guitar = 기타; ) 공개 void play() ( 기타.play(); ) )

공개 클래스 음유시인(

개인 기타 기타;

퍼블릭 바드(기타 기타)

이것. 기타 = 기타 ;

공개 무효 플레이()

기타 놀다();

이 클래스에 다른 악기에 대한 지원을 추가하려면 어떤 방식으로든 이 클래스를 수정해야 합니다. 이는 명백한 원칙 위반이다. OCP. 그리고 이것이 원칙을 위반한다는 사실을 이미 알아차렸을 수도 있습니다. 담그다, 우리의 경우 추상화가 세부 사항에 의존하는 것으로 밝혀졌기 때문입니다. 우리 수업의 추가 확장이라는 관점에서 볼 때 이것은 전혀 좋지 않습니다. 우리 수업이 원칙의 조건을 충족하도록 OCP시스템에 인터페이스를 추가했습니다 기구, 이는 특정 유형의 악기를 나타내는 특정 클래스에 의해 구현되었습니다.

파일 Instrument.java:

공용 인터페이스 악기( void play(); )

공용 인터페이스 계측기(

무효 플레이();

파일 Guitar.java:

class Guitar는 Instrument를 구현합니다( @Override public void play() ( System.out.println("Play Guitar!"); ) )

클래스 기타 구현 악기 (

@우세하다

공개 무효 플레이()

체계. 밖으로 . println("기타를 연주하세요!");

파일 Lute.java:

공용 클래스 Lute는 악기를 구현합니다( @Override public void play() ( System.out.println("Play Lute!"); ) )

공개 클래스 Lute는 악기를 구현합니다(

@우세하다

공개 무효 플레이()

체계. 밖으로 . println("류트를 연주하세요!");

그 후 우리는 수업을 바꿨습니다 음유 시인, 필요한 경우 구현을 정확히 필요한 구현으로 대체할 수 있습니다. 이는 생성된 시스템에 추가적인 유연성을 제공하고 결합(클래스 간의 강한 종속성)을 줄입니다.

공개 클래스 Bard ( 개인 악기 악기; 공개 Bard() ( ) 공개 void play() ( 악기.play(); ) 공개 void setInstrument(악기 악기) ( this.instrument = 악기; ) )

공개 클래스 음유시인(

개인 악기 악기 ;

종속성 반전은 가장 중요한 프로그래밍 관용어 중 하나입니다. 러시아어 인터넷에는 이 관용어(원리)에 대한 설명이 놀랍게도 거의 없습니다. 그래서 나는 설명을 만들어보기로 결정했습니다. 저는 Java로 예제를 작성하겠습니다. 이 순간종속성 반전의 원칙은 모든 프로그래밍 언어에 적용되지만 저에게는 더 쉽습니다.

이 설명은 Java를 공부하는 학생들의 수업을 준비하기 위해 Vladimir Matveev와 공동으로 개발되었습니다.

이 시리즈의 다른 기사:

"중독"의 정의부터 시작하겠습니다. 중독이란 무엇입니까? 코드에서 일부 클래스를 내부적으로 사용하거나 일부 클래스 또는 함수의 정적 메서드를 명시적으로 호출하는 경우 이는 종속성입니다. 예를 들어 설명하겠습니다.

아래에서 클래스 A는 someMethod()라는 메서드 내에서 클래스 B의 객체를 명시적으로 생성하고 해당 메서드 someMethodOfB()를 호출합니다.

공용 클래스 A ( void someMethod() ( B b = new B(); b.someMethodOfB(); ) )

마찬가지로, 예를 들어 클래스 B는 System 클래스의 정적 필드와 메서드에 명시적으로 액세스합니다.

공용 클래스 B ( void someMethodOfB() ( System.out.println("Hello world"); ) )

모든 경우에 임의의 클래스(유형 A)가 임의의 클래스(유형 B)를 독립적으로 생성하거나 정적 필드 또는 클래스 멤버에 명시적으로 액세스하는 경우 이를 호출합니다. 똑바로탐닉. 저것들. 중요: 자체 내의 클래스가 다른 클래스와 함께 작동하는 경우 이는 종속성입니다. 내부에 이 클래스도 생성한다면, 똑바로탐닉.

직접 종속성의 문제점은 무엇입니까? 자체적으로 다른 클래스를 독립적으로 생성하는 클래스는 이 클래스에 "단단히" 연결되어 있기 때문에 직접적인 종속성은 좋지 않습니다. 저것들. B = new B()라고 명시적으로 작성된 경우; , 그러면 클래스 A는 항상 클래스 B와 함께 작동하고 다른 클래스는 작동하지 않습니다. 또는 System.out.println("..."); 그러면 클래스는 항상 System.out으로 출력되고 다른 곳에서는 출력되지 않습니다.

소규모 클래스의 경우 종속성은 나쁘지 않습니다. 이 코드는 꽤 잘 작동할 수 있습니다. 그러나 어떤 경우에는 클래스 A가 다른 클래스의 환경에서 보편적으로 작동할 수 있으려면 클래스의 다른 구현, 즉 종속성이 필요할 수 있습니다. 저것들. 예를 들어, 클래스 B가 필요하지 않고 동일한 인터페이스를 가진 다른 클래스가 필요하거나 System.out이 필요하지 않지만 예를 들어 로거(예: log4j)로 출력해야 합니다.

직접적인 관계는 다음과 같이 그래픽으로 표시될 수 있습니다.

저것들. 코드에서 클래스 A를 생성할 때: A a = new A(); 실제로 하나의 클래스 A가 생성되는 것이 아니라 종속 클래스의 전체 계층 구조가 생성됩니다. 그 예는 아래 그림에 나와 있습니다. 이 계층 구조는 "고정적"입니다. 즉, 개별 클래스의 소스 코드를 변경하지 않으면 계층 구조의 어떤 클래스도 교체할 수 없습니다. 따라서 이러한 구현의 클래스 A는 변화하는 환경에 제대로 적응하지 못합니다. 대부분의 경우 작성한 특정 코드 이외의 다른 코드에서는 사용할 수 없습니다.

클래스 A를 특정 종속성에서 분리하려면 다음을 사용하세요. 의존성 주입. 의존성 주입이란 무엇입니까? 코드에서 필요한 클래스를 명시적으로 생성하는 대신 생성자를 통해 종속성이 클래스 A에 전달됩니다.

공개 클래스 A ( 비공개 최종 B b; 공개 A(B b) ( this.b = b; ) 공개 void someMethod() ( b.someMethodOfB(); ) )

저것. 이제 클래스 A는 생성자를 통해 종속성을 받습니다. 이제 클래스 A를 만들려면 먼저 종속 클래스를 만들어야 합니다. 안에 이 경우 B예요:

Bb = 새로운 B(); A a = 새로운 A(b); a.someMethod();

모든 클래스에 대해 동일한 절차가 반복되면, 즉 클래스 D의 인스턴스를 클래스 B의 생성자에 전달하고 종속성 E와 F를 클래스 D의 생성자에 전달하면 모든 종속성이 역순으로 생성되는 코드를 얻게 됩니다.

G g = 새로운 G(); H h = 새로운 H(); F f = 새로운 (g,h); E e = 새로운 E(); D d = 새로운 D(e,f); B b = 새로운 B(d); A a = 새로운 A(b); a.someMethod();

이는 다음과 같이 그래픽으로 표시될 수 있습니다.

두 그림(직접 종속성을 적용한 위의 그림과 종속성 주입을 적용한 두 번째 그림)을 비교해 보면 화살표 방향이 반대 방향으로 바뀐 것을 확인할 수 있습니다. 이러한 이유로 이 관용어를 종속성의 "역전"이라고 합니다. 즉, 종속성 반전은 클래스가 자체적으로 종속성을 생성하지 않고 생성자(또는 다른 방식)에서 생성된 형식으로 이를 받는 것을 의미합니다.

의존성 역전이 왜 좋은가요? 종속성 반전을 사용하면 코드를 변경하지 않고도 클래스의 모든 종속성을 바꿀 수 있습니다. 이는 클래스 A가 원래 작성된 프로그램이 아닌 다른 프로그램에서 사용하도록 유연하게 구성할 수 있음을 의미합니다. 저것. 종속성 반전 원칙(종속성 주입 원칙이라고도 함)은 유연하고 재사용 가능한 모듈식 코드를 구축하는 데 핵심입니다.

종속성 주입의 단점도 언뜻 보면 알 수 있습니다. 이 패턴을 사용하여 설계된 클래스의 개체는 구성하는 데 노동 집약적입니다. 따라서 종속성 주입은 일반적으로 이 작업을 용이하게 하기 위해 설계된 일부 라이브러리와 함께 사용됩니다. 예를 들어 Google Guice 라이브러리 중 하나입니다. 센티미터. .

답변 2개

좋은 질문입니다. 반전이라는 단어는 다소 놀랍습니다(DIP를 적용한 후 하위 수준 종속성 모듈은 이제 호출자 모듈에 더 이상 의존하지 않기 때문입니다). 높은 레벨: 이제 호출자 또는 종속 항목이 추가 추상화를 통해 더 느슨하게 결합됩니다.

내가 왜 "역전"이라는 단어를 사용하는지 묻는 사람도 있을 것입니다. 공평하게 말하면, 구조화된 분석 및 설계와 같은 보다 전통적인 소프트웨어 개발 방법은 상위 수준 모듈이 하위 수준 모듈에 의존하고 추상화가 세부 사항에 의존하는 소프트웨어 구조를 만드는 경향이 있기 때문입니다. 실제로 이러한 방법의 목적 중 하나는 상위 수준 모듈이 하위 수준 모듈을 호출하는 방법을 설명하는 서브루틴 계층 구조를 정의하는 것입니다.... 따라서 잘 설계된 객체 지향 프로그램의 종속성 구조는 " 일반적으로 전통적인 절차적 방법의 결과인 종속성 구조에 비해 "역전"된 것입니다.

DIP에 대한 Bob 삼촌의 논문을 읽을 때 주의할 점은 C++에는 인터페이스가 없다는 것입니다(그리고 이 글을 쓰는 시점에도 인터페이스가 없습니다). 따라서 C++에서 이러한 추상화를 달성하는 것은 일반적으로 추상/순수 가상 기본 클래스를 통해 구현됩니다. Java 또는 C# 결합을 느슨하게 하기 위한 추상화는 일반적으로 종속성에서 인터페이스를 추상화하고 상위 수준 모듈을 인터페이스에 결합하여 분리하는 것입니다.

편집하다다시 한번 확인하기 위해:

"어떤 곳에서는 이를 종속성 역전이라고 부르기도 합니다."

반전:종속성 관리를 애플리케이션에서 컨테이너(예: Spring)로 전환합니다.

의존성 주입:

팩토리 템플릿을 작성하는 대신 클라이언트 클래스에 객체를 직접 삽입하는 것은 어떻습니까? 따라서 클라이언트 클래스가 인터페이스를 참조하도록 하면 클라이언트 클래스에 구체적인 유형을 주입할 수 있어야 합니다. 이를 통해 클라이언트 클래스는 new 키워드를 사용할 필요가 없으며 구체적인 클래스와 완전히 분리됩니다.

제어 반전(IoC)은 어떻습니까?

기존 프로그래밍에서 비즈니스 로직의 흐름은 서로 정적으로 할당된 개체에 의해 정의됩니다. 제어 역전의 경우 흐름은 어셈블러 인스턴스에 의해 생성되고 추상화를 통해 정의된 개체 상호 작용에 의해 가능해지는 개체 그래프에 따라 달라집니다. 서비스 로케이터를 사용하면 제어 반전도 제공된다고 주장하는 사람들도 있지만 바인딩 프로세스는 종속성 주입을 통해 달성됩니다.

설계 지침으로서의 제어 반전은 다음과 같은 목적으로 사용됩니다.

  • 특정 작업의 실행과 구현이 분리되어 있습니다.
  • 각 모듈은 수행하려는 작업에 집중할 수 있습니다.
  • 모듈은 다른 시스템이 수행하는 작업에 대해 가정하지 않지만 계약에 의존합니다.
  • 모듈을 교체해도 다른 모듈에는 영향을 미치지 않습니다.

얻기 위해 추가 정보바라보다.

답변 14개

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

  • 추상화는 결코 세부사항에 의존해서는 안 됩니다. 세부 사항은 추상화에 따라 달라져야 합니다.

이것이 중요한 이유는 한마디로 말하면 변경은 위험하며 구현보다는 개념에 따라 호출 사이트에서 변경의 필요성이 줄어드는 것입니다.

효과적으로 DIP는 코드의 여러 부분 간의 결합을 줄입니다. 로거 등을 구현하는 방법은 다양하지만 이를 사용하는 방식은 시간이 지나도 상대적으로 안정적이어야 한다는 아이디어입니다. 로깅 개념을 나타내는 인터페이스를 추출할 수 있는 경우 해당 인터페이스는 구현보다 시간이 지남에 따라 훨씬 더 안정적이어야 하며 호출 사이트는 해당 로깅 메커니즘을 유지 관리하거나 확장하여 수행할 수 있는 변경 사항에 훨씬 덜 민감해야 합니다.

구현은 인터페이스마다 다르므로 런타임 시 특정 환경에 가장 적합한 구현을 선택할 수 있습니다. 경우에 따라서는 이것도 흥미로울 수 있습니다.

Agile Software Development, 원칙, 패턴 및 사례 및 Agile 원칙, 패턴 및 사례 C# 책은 종속성 역전 원칙의 원래 목표와 동기를 완전히 이해하기 위한 최고의 리소스입니다. "의존성 역전 원칙"이라는 기사도 좋은 자료이지만 이전에 언급한 책에서 결국 나온 초안의 압축 버전이기 때문에 패키지 소유권 개념에 대한 몇 가지 중요한 논의가 남아 있습니다. 이 원칙은 Design Patterns(Gamma, et al.) 책에 나오는 "구현이 아닌 인터페이스를 위한 프로그램"에 대한 보다 일반적인 조언과 다릅니다.

간단히 요약하면 종속성 역전의 원칙은 주로 다음을 목표로 합니다. 변화전통적으로 "상위 수준" 구성 요소에서 "하위 수준" 구성 요소로 종속성을 채널링하여 "하위 수준" 구성 요소가 인터페이스에 종속되도록 합니다. 소유(참고: 여기서 "상위 수준" 구성 요소는 외부 종속성/서비스가 필요한 구성 요소를 의미하며 반드시 계층화된 아키텍처의 개념적 위치를 의미하는 것은 아닙니다.) 그러나 관계는 그렇지 않습니다. 감소하다그녀만큼 교대이론적으로 덜 가치 있는 구성 요소부터 이론적으로 더 가치 있는 구성 요소까지.

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

종속성 역전 원칙이 다루지 않는 것은 인터페이스(예: MyService -> )를 사용하여 종속성을 추상화하는 간단한 방법입니다. 이는 종속성의 특정 구현 세부 사항에서 구성 요소를 분리하지만 소비자와 종속성 간의 관계를 반전시키지는 않습니다(예: ⇐ Logger.

종속성 역전 원칙의 중요성은 기능(등록, 확인 등)의 일부에 대해 외부 종속성에 의존하는 소프트웨어 구성 요소를 재사용하는 기능이라는 단일 목표로 요약될 수 있습니다.

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

    종속성 구현이 포함된 여러 애플리케이션에서 소프트웨어 구성 요소 사용(예: DI 컨테이너를 개발하고 로깅을 제공하고 싶지만 컨테이너를 특정 로거에 연결하고 싶지 않으므로 컨테이너를 사용하는 모든 사람도 로깅을 사용해야 함) 당신이 선택하는 도서관).

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

인프라 라이브러리와 같이 여러 애플리케이션에서 구성 요소를 재사용하는 첫 번째 경우 목표는 소비자를 자체 라이브러리의 종속성에 묶지 않고 소비자에게 기본 인프라를 제공하는 것입니다. 이러한 종속성에서 종속성을 얻으려면 소비자도 다음을 요구해야 하기 때문입니다. 동일한 종속성. 이는 라이브러리 소비자가 동일한 인프라 요구 사항(예: NLog 및 log4net)에 대해 다른 라이브러리를 사용하기로 결정하거나 필요한 버전과 이전 버전과 호환되지 않는 필수 라이브러리의 이후 버전을 사용하기로 결정한 경우 문제가 될 수 있습니다. 당신의 도서관에서.

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

메모. 어떤 사람들은 단일 개발 애플리케이션에 사용되는 비즈니스 로직 구성 요소와 같은 구성 요소가 단 한 번만 사용된다고 믿고 이 두 번째 사례를 실제 재사용으로 설명하는 데 반대할 수 있습니다. 그러나 여기서의 아이디어는 애플리케이션 구현 세부 사항의 모든 변경 사항이 새로운 컨텍스트를 나타내며 따라서 최종 목표는 격리와 이식성으로 구분될 수 있지만 다른 사용 사례를 나타낸다는 것입니다.

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

이 원칙에 관한 보다 자세한 논의는 다음과 같습니다. 사용하기 쉬운인터페이스, 종속성 주입 및 분할 인터페이스 패턴을 찾을 수 있습니다.

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

후자는 낮은 수준의 클래스에 의존합니다. 그러한 구조를 구현하는 자연스러운 방법은 낮은 수준의 클래스를 작성하고 복잡한 높은 수준의 클래스를 작성해야 할 때마다 작성하는 것입니다. 상위 클래스는 다른 사람의 관점에서 정의되기 때문에 이것이 논리적인 방법인 것 같습니다. 하지만 이는 반응형 디자인이 아닙니다. 낮은 수준의 클래스를 교체해야 하면 어떻게 되나요?

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

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

이 원칙은 상위 수준 모듈이 소프트웨어하위 레벨 모듈에 의존해야 합니다. 여기서 상위 수준 모듈은 하위 수준 모듈에서 구현되는 추상화(예: 인터페이스 메서드 결정)를 소유합니다. 따라서 하위 레벨 모듈은 상위 레벨 모듈에 의존합니다.

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

전통적인 계층형 아키텍처

전통적으로 계층화된 아키텍처의 사용자 인터페이스는 비즈니스 계층에 의존했고, 비즈니스 계층은 다시 데이터 액세스 계층에 의존했습니다.

레이어, 패키지 또는 라이브러리를 이해해야 합니다. 코드가 어떻게 진행되는지 살펴보겠습니다.

데이터 액세스 계층을 위한 라이브러리나 패키지가 있을 것입니다.

// DataAccessLayer.dll 공용 클래스 ProductDAO( )

// DataAccessLayer를 사용하는 BusinessLogicLayer.dll; 공개 클래스 ProductBO( 비공개 ProductDAO productDAO; )

종속성 반전을 갖춘 계층형 아키텍처

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

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

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

상위 레벨 모듈과 하위 레벨 모듈은 무엇입니까? 라이브러리나 패키지와 같은 모듈을 생각하면 상위 수준 모듈은 전통적으로 종속성을 갖는 모듈이고 하위 수준 모듈은 의존하는 모듈입니다.

즉, 모듈의 상위 레벨은 작업이 호출되는 위치이고 하위 레벨은 작업이 실행되는 위치입니다.

이 원칙으로부터 합리적인 결론을 도출할 수 있습니다. 즉, 결절 사이에는 종속성이 없어야 하지만 추상화에는 종속성이 있어야 합니다. 그러나 우리가 취하는 접근 방식에 따르면 투자 의존도를 잘못 사용하고 있을 수도 있지만 이는 추상적인 개념입니다.

코드를 다음과 같이 조정한다고 상상해 보세요.

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

// DataAccessLayer.dll 공용 인터페이스 IProductDAO 공용 클래스 ProductDAO: IProductDAO( )

그리고 데이터 액세스 계층에 의존하는 기타 라이브러리 또는 패키지 수준 비즈니스 논리.

// DataAccessLayer를 사용하는 BusinessLogicLayer.dll; 공개 클래스 ProductBO( 비공개 IProductDAO productDAO; )

우리는 추상화에 의존하지만 비즈니스와 데이터 액세스 간의 관계는 동일하게 유지됩니다.

종속성 반전을 달성하려면 하위 수준 모듈이 아닌 상위 수준 논리 또는 도메인이 상주하는 모듈이나 패키지에 지속성 인터페이스를 정의해야 합니다.

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

// Domain.dll 공용 인터페이스 IProductRepository; DataAccessLayer 사용; 공개 클래스 ProductBO( 비공개 IProductRepository productRepository; )

지속성 수준이 도메인에 따라 달라지면 이제 종속성이 정의된 경우 반전이 가능합니다.

// Persistence.dll 공용 클래스 ProductDAO: IProductRepository( )

원리 심화

개념을 잘 이해하고 목적과 이점을 심화시키는 것이 중요합니다. 역학에 머물면서 일반적인 저장소를 연구한다면 종속성 원칙을 어디에 적용할 수 있는지 결정할 수 없을 것입니다.

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

이것은 일반적으로 덜 안정적인 것으로부터 독립적인 가장 안정적인 것이 더 자주 변경되도록 허용합니다.

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

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

건축학

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

클린 아키텍처

나에게 공식 기사에 설명된 종속성 반전 원칙은 다음과 같습니다.

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

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

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

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

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

즉, 사람들이 일반적으로 하는 것처럼 Logic 클래스를 구현하는 대신 다음을 수행합니다.

클래스 종속성 ( ... ) class Logic ( private 종속성 dep; int doSomething() ( // 여기서 dep를 사용하는 비즈니스 로직 ) )

다음과 같이 해야 합니다:

클래스 종속성 ( ... ) 인터페이스 데이터 ( ... ) 클래스 DataFromDependency 구현 데이터 ( 개인 종속성 dep; ... ) 클래스 논리 ( int doSomething(Data data) ( // 데이터로 무언가 계산 ) )

Data 및 DataFromDependency는 종속성이 아닌 Logic과 동일한 모듈에 있어야 합니다.

왜 이런거야?

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

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

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

이는 일반적으로 Java의 Spring과 같은 컨트롤 컨테이너(IoC)를 반전하여 달성됩니다. 이 모델에서는 개체가 나가서 종속성을 찾는 것이 아니라 개체의 속성이 XML 구성을 통해 설정됩니다.

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

공용 클래스 MyClass( 공용 서비스 myService = ServiceLocator.service; )

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

공용 클래스 MyClass( public IService myService; )

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

식품 제조업체에 물품을 요청하는 호텔이 있다고 가정해 보겠습니다. 호텔은 음식 이름(예: 닭고기)을 Food Generator에 제공하고 Generator는 요청한 음식을 호텔에 반환합니다. 그러나 호텔은 어떤 종류의 음식을 얻고 제공하는지에는 관심이 없습니다. 따라서 Generator는 "Food"라고 표시된 음식을 호텔에 공급합니다.

이 구현은 JAVA에서

팩토리 메소드가 있는 FactoryClass. 식품 생성기

Public 클래스 FoodGenerator ( 음식 음식; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken(); )else 음식 = null; 음식 반환; ) )

클래스 주석/인터페이스

Public 추상 클래스 Food( //어느 하위 클래스도 품질을 보장하기 위해 이 메서드를 재정의하지 않습니다... public void quality())( String fresh = "This is a fresh " + getName(); String Delicious = "This is a 맛있는 " + getName(); System.out.println(fresh); System.out.println(tasty); ) 공개 추상 문자열 getName(); )

치킨 판매 음식 (콘크리트 클래스)

Public 클래스 Chicken 확장 Food ( /*모든 음식 유형은 신선하고 맛있어야 하므로 * 슈퍼 클래스 메서드 "property()"를 재정의하지 않습니다.*/ public String getName())( return "Chicken"; ) )

물고기는 음식을 판매합니다(특정 클래스)

Public 클래스 Fish는 Food를 확장합니다( /*모든 음식 유형은 신선하고 맛있어야 하므로 * 슈퍼 클래스 메서드 "property()"를 재정의하지 않습니다.*/ public String getName())( return "Fish"; ) )

마지막으로

호텔

Public class Hotel ( public static void main(String args)( //Factory 클래스 사용.... FoodGenerator foodGenerator = new FoodGenerator(); //음식을 인스턴스화하는 팩토리 메서드... Food food = foodGenerator.getFood( "닭고기"); food.quality(); ) )

보시다시피 호텔에서는 닭고기인지 생선인지 모릅니다. 이것이 식품이라는 것만 알려져 있습니다. 호텔은 음식 등급에 따라 다릅니다.

Fish 및 Chicken 클래스는 Food 클래스를 구현하며 호텔과 직접적으로 연결되지 않는다는 점도 알 수 있습니다. 저것들. 닭고기와 생선도 식품 등급에 따라 다릅니다.

이는 상위 수준 구성 요소(호텔)와 하위 수준 구성 요소(생선 및 닭고기)가 추상화(음식)에 의존한다는 의미입니다.

이를 종속성 역전이라고 합니다.

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

i) 상위 모듈은 하위 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다.

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

공용 인터페이스 ICustomer ( string GetCustomerNameById(int id); ) 공용 클래스 Customer: ICustomer ( //ctor public Customer()() 공용 문자열 GetCustomerNameById(int id) ( return "Dummy Customer Name"; ) ) 공용 클래스 CustomerFactory ( public static ICustomer GetCustomerData() ( 새 Customer() 반환; ) ) 공개 클래스 CustomerBLL ( ICustomer _customer; 공개 CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) 공개 문자열 GetCustomerNameById(int id) ( return _customer.GetCustomerNameById(id); ) ) 공용 클래스 프로그램 ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

메모. 클래스는 구체적인 세부 사항(인터페이스 구현)보다는 인터페이스나 추상 클래스와 같은 추상화에 의존해야 합니다.

공유하다

마지막 업데이트: 2016년 3월 11일

종속성 반전 원리종속성 반전 원칙은 테스트, 수정 및 업데이트가 쉬운 느슨하게 결합된 엔터티를 만드는 데 사용됩니다. 이 원칙은 다음과 같이 공식화될 수 있습니다.

최상위 모듈은 하위 모듈에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다.

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

원리를 이해하려면 다음 예를 고려하십시오.

클래스 북( 공개 문자열 텍스트( 가져오기; 설정; ) 공개 ConsolePrinter 프린터( 가져오기; 설정; ) 공개 void Print()( Printer.Print(Text); ) ) 클래스 ConsolePrinter( 공개 void Print(문자열 텍스트)( Console.WriteLine (텍스트); ) )

책을 나타내는 Book 클래스는 ConsolePrinter 클래스를 사용하여 인쇄합니다. 이 정의를 사용하면 Book 클래스는 ConsolePrinter 클래스에 종속됩니다. 또한 책 인쇄는 ConsolePrinter 클래스를 사용하여 콘솔에서만 수행할 수 있다고 엄격하게 정의했습니다. 예를 들어 프린터로 출력, 파일로 출력 또는 일부 그래픽 인터페이스 요소 사용과 같은 다른 옵션은 이 경우 모두 제외됩니다. 책 인쇄 추상화는 ConsolePrinter 클래스의 세부 사항과 분리되지 않습니다. 이 모든 것은 종속성 역전 원칙을 위반하는 것입니다.

이제 하위 수준 구현에서 추상화를 분리하여 종속성 반전 원칙에 맞춰 클래스를 정렬해 보겠습니다.

인터페이스 IPrinter( void Print(string text); ) 클래스 Book( 공용 문자열 텍스트( get; set; ) 공용 IPrinter Printer( get; set; ) 공용 Book(IPrinter 프린터)( this.Printer = 프린터; ) public void Print( ) ( Printer.Print(Text); ) ) 클래스 ConsolePrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("콘솔로 인쇄"); ) ) 클래스 HtmlPrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("html로 인쇄"); ) )

이제 책 인쇄 추상화는 구체적인 구현과 분리되었습니다. 결과적으로 Book 클래스와 ConsolePrinter 클래스는 모두 IPrinter 추상화에 의존합니다. 또한 이제 IPrinter 추상화의 추가 하위 수준 구현을 생성하고 이를 프로그램에 동적으로 적용할 수도 있습니다.

책 book = new Book(new ConsolePrinter()); 책.인쇄(); book.Printer = 새로운 HtmlPrinter(); 책.인쇄();