의존성 주입을 사용하는 이유는 무엇입니까?


536

의존성 주입 (DI) 을 이해하려고하는데 다시 실패했습니다. 어리석은 것 같습니다. 내 코드는 결코 엉망이 아닙니다. 나는 가상 함수와 인터페이스를 거의 작성하지 않으며 (푸른 달에 한 번만 사용하더라도) 모든 구성은 json.net (때로는 XML 직렬 변환기 사용)을 사용하여 마술처럼 클래스로 직렬화됩니다.

어떤 문제가 해결되는지 잘 모르겠습니다. "hi.이 함수를 실행하면이 유형의 객체를 반환하고 이러한 매개 변수 / 데이터를 사용합니다."
하지만 ... 왜 내가 그걸 사용 하겠어? 참고로 사용할 필요는 없지만 object그 목적이 무엇인지 이해합니다.

DI를 사용하는 웹 사이트 또는 데스크톱 응용 프로그램을 구축 할 때 실제 상황은 무엇입니까? 누군가가 게임에서 인터페이스 / 가상 기능을 사용하려는 이유에 대한 사례를 쉽게 생각 해낼 수 있지만, 게임 이외의 코드에서 사용하는 것은 매우 드 (니다 (단일 인스턴스를 기억할 수 없을 정도로 드 ra니다).


3
이것은 또한 유용한 정보 일 수 있습니다 : martinfowler.com/articles/injection.html
ta.speot.is은



41
저는이 사람과 함께 있습니다 : jamesshore.com/Blog/Dependency-Injection-Demystified.html .
Eric Lippert

5
DI에 대한 또 다른 매우 간단한 설명 : codearsenal.net/2015/03/…
ybonda

답변:


840

먼저이 답변에 대한 가정을 설명하고 싶습니다. 항상 사실은 아니지만 매우 자주 :

인터페이스는 형용사입니다. 수업은 명사입니다.

(실제로 명사 인 인터페이스가 있지만 여기서는 일반화하고 싶습니다.)

따라서, 인터페이스를 예하는 등 뭔가 할 수있다 IDisposable, IEnumerable또는 IPrintable. 클래스는 이러한 인터페이스 중 하나 이상을 실제로 구현 한 것입니다. List또는 Map둘 다의 구현 일 수 있습니다 IEnumerable.

요점을 파악하려면 : 종종 수업이 서로에게 의존합니다. 예를 들어 Database데이터베이스에 액세스 하는 클래스가 있을 수 있지만 (ha, surprise! ;-))이 클래스가 데이터베이스 액세스에 대한 로깅을 수행하기를 원할 수도 있습니다. 다른 클래스가 있다고 가정 Logger다음 Database에 대한 종속성이 있습니다 Logger.

여태까지는 그런대로 잘됐다.

Database다음 행을 사용 하여 클래스 내에서이 종속성을 모델링 할 수 있습니다 .

var logger = new Logger();

그리고 모든 것이 괜찮습니다. 많은 로거가 필요하다는 것을 깨달을 때까지는 괜찮습니다. 때로는 콘솔, 때로는 파일 시스템, 때로는 TCP / IP 및 원격 로깅 서버 등을 사용하여 로그하려는 경우가 있습니다 ...

그리고 물론 모든 코드를 변경하고 싶지 는 않지만 모든 코드를 변경하고 싶지 는 않습니다.

var logger = new Logger();

으로:

var logger = new TcpLogger();

첫째, 이것은 재미 없다. 둘째, 오류가 발생하기 쉽습니다. 셋째, 이것은 훈련 된 원숭이를위한 어리 석고 반복적 인 작업입니다. 그래서 당신은 무엇을합니까?

분명히 ICanLog모든 다양한 로거가 구현 하는 인터페이스 (또는 유사한) 를 도입하는 것이 좋습니다 . 따라서 코드의 1 단계는 다음과 같습니다.

ICanLog logger = new Logger();

이제 타입 추론이 더 이상 타입을 변경하지 않고 개발할 인터페이스가 항상 하나 있습니다. 다음 단계는 계속해서 new Logger()반복하고 싶지 않다는 것 입니다. 따라서 단일 인스턴스 팩토리 클래스에 새 인스턴스를 생성 할 수있는 안정성을 제공하고 다음과 같은 코드를 얻습니다.

ICanLog logger = LoggerFactory.Create();

팩토리 자체는 어떤 종류의 로거를 작성할 것인지 결정합니다. 코드는 더 이상 신경 쓰지 않으며 사용중인 로거 유형을 변경하려면 한 번만 변경하십시오 . 공장 내부.

물론이 팩토리를 일반화하고 모든 유형에 적용 할 수 있습니다.

ICanLog logger = TypeFactory.Create<ICanLog>();

이 TypeFactory에는 특정 인터페이스 유형이 요청 될 때 인스턴스화 할 실제 클래스에 대한 구성 데이터가 필요하므로 맵핑이 필요합니다. 물론 코드 내 에서이 매핑을 수행 할 수 있지만 유형 변경은 재 컴파일을 의미합니다. 그러나이 매핑을 XML 파일 안에 넣을 수도 있습니다. 이렇게하면 컴파일 시간 (!) 후에도 실제로 사용되는 클래스를 변경할 수 있습니다. 다시 컴파일하지 않고도 동적으로 의미합니다!

유용한 예를 들어 보자 : 정상적으로 기록되지는 않지만 고객이 문제가있어 도움을 요청하는 경우, 업데이트 된 XML 구성 파일 만 있으면됩니다. 로깅이 사용 가능하며 고객 지원이 로그 파일을 사용하여 고객을 도울 수 있습니다.

이제 이름을 약간 바꾸면 Service Locator 의 간단한 구현으로 끝납니다 . 이것은 Inversion of Control 의 두 가지 패턴 중 하나입니다 (인스턴스를 생성 할 정확한 클래스를 결정하는 사람에 대한 제어를 반전 시키기 때문에).

이 모든 것이 코드의 종속성을 줄여 주지만 이제 모든 코드는 중앙의 단일 서비스 로케이터에 대한 종속성을 갖습니다.

의존성 주입 은 이제이 라인의 다음 단계입니다. 서비스 로케이터에 대한이 단일 의존성을 제거하십시오. 특정 인터페이스에 대한 구현을 서비스 로케이터에게 요청하는 다양한 클래스 대신, 다시 한 번, 누가 무엇을 인스턴스화하는지에 대한 제어를 되돌립니다. .

의존성 주입을 사용하면 Database클래스에 다음 유형의 매개 변수가 필요한 생성자가 있습니다 ICanLog.

public Database(ICanLog logger) { ... }

이제 데이터베이스에는 항상 사용할 로거가 있지만이 로거의 출처는 더 이상 알 수 없습니다.

그리고 이것이 DI 프레임 워크가 작동하는 곳입니다. 다시 한 번 매핑을 구성한 다음 DI 프레임 워크에 응용 프로그램을 인스턴스화하도록 요청하십시오. 애즈 Application클래스가 필요 ICanPersistData구현의 인스턴스가 Database주입 -하지만 것은 제 구성된 로거 종류의 인스턴스를 생성한다 ICanLog. 등등 ...

간단히 말해, 의존성 주입은 코드에서 의존성을 제거하는 두 가지 방법 중 하나입니다. 컴파일 타임 후 구성 변경에 매우 유용하며, 스텁 및 / 또는 모의 객체를 매우 쉽게 주입 할 수 있으므로 단위 테스트에 유용합니다.

실제로 서비스 로케이터 없이는 할 수없는 일이 있습니다 (예 : 특정 인터페이스에 필요한 인스턴스 수를 미리 알지 못하는 경우 : DI 프레임 워크는 항상 매개 변수 당 하나의 인스턴스 만 삽입하지만 호출 할 수는 있음) 물론 루프 내부의 서비스 로케이터), 따라서 대부분의 각 DI 프레임 워크는 또한 서비스 로케이터를 제공합니다.

그러나 기본적으로 그게 다입니다.

추신 : 여기에 설명 한 것은 생성자 주입 이라는 기술입니다. 생성자 매개 변수가 아닌 속성 주입 도 있지만 속성은 종속성을 정의하고 해결하는 데 사용됩니다. 속성 삽입은 선택적 종속성으로, 생성자 삽입은 필수 종속성으로 생각하십시오. 그러나 이에 대한 논의는이 질문의 범위를 벗어납니다.


7
물론 이런 식으로도 할 수 있지만 구현의 교환 가능성을 지원하는 모든 단일 클래스 에서이 논리를 구현해야합니다. 이는 중복 된 중복 코드가 많음을 의미하며, 지금 필요하다고 판단되면 기존 클래스를 터치하고 부분적으로 다시 작성해야합니다. DI를 사용하면 임의의 클래스에서이를 사용할 수 있으므로 생성자를 매개 변수로 종속성을 정의하는 것을 제외하고는 특별한 방식으로 작성할 필요가 없습니다.
Golo Roden

137
DI에 대해 결코 알 수없는 것은 아키텍처를 훨씬 더 복잡하게 만듭니다. 그럼에도 불구하고, 사용은 꽤 제한적입니다. 상호 교환 가능한 로거, 상호 교환 가능한 모델 / 데이터 액세스의 예는 항상 동일합니다. 때때로 상호 교환 가능한 관점. 하지만 그게 다야. 이 몇 가지 사례가 훨씬 더 복잡한 소프트웨어 아키텍처를 실제로 정당화합니까? – 전체 공개 : 이미 DI를 사용하여 큰 효과를 얻었지만 일반화하지 않은 매우 특별한 플러그인 아키텍처였습니다.
Konrad Rudolph

17
@GoloRoden, 왜 ILogger 대신 ICanLog 인터페이스를 호출합니까? 나는 종종 이것을 한 다른 프로그래머와 함께 일했는데, 나는 그 협약을 이해할 수 없었습니까? 나에게 IEnumerable ICanEnumerate를 호출하는 것과 같다?
DermFrench

28
우리는 아무 의미없는 단어 (명사)로 너무 자주 일하기 때문에 ICanLog라고 불렀습니다. 예를 들어 브로커 란 무엇입니까? 관리자? 리포지토리조차도 고유 한 방식으로 정의되지 않았습니다. 이 모든 것을 명사로 사용하는 것은 OO 언어의 전형적인 질병입니다 ( steve-yegge.blogspot.de/2006/03/… 참조 ). 내가 표현하고 싶은 것은 내가 로깅 할 수있는 구성 요소가 있다는 것입니다. 그래서 그렇게 부르지 않는 이유는 무엇입니까? 물론 이것은 또한 I를 첫 번째 사람으로 사용하므로 ICanLog (ForYou)입니다.
Golo Roden

18
@David Unit 테스트는 제대로 작동합니다. 결국 장치 는 다른 것과 독립적입니다 (그렇지 않으면 장치가 아닙니다). 무엇을 하지 않습니다 DI 컨테이너없이 작동하는 모의 테스트입니다. 공정하게, 나는 조롱의 이점이 모든 경우에 DI 컨테이너를 추가하는 추가 복잡성을 능가한다고 확신하지 않습니다. 엄격한 단위 테스트를 수행합니다. 나는 조롱을 거의하지 않습니다.
Konrad Rudolph

499

나는 사람들이 의존성 주입 과 의존성 주입 프레임 워크 (또는 종종 컨테이너 라고도 함) 의 차이점에 대해 많은 혼란을 겪고 있다고 생각합니다 .

의존성 주입은 매우 간단한 개념입니다. 이 코드 대신 :

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

다음과 같은 코드를 작성하십시오.

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

그리고 그게 다야. 진심으로. 이것은 당신에게 많은 장점을 제공합니다. 두 가지 중요한 것은 Main()프로그램 전체에 분산시키는 대신 중앙 위치 ( 함수) 에서 기능을 제어하는 ​​기능 과 모의 객체 또는 다른 위조 된 객체를 생성자에 대신 전달할 수 있기 때문에 각 클래스를보다 쉽게 ​​테스트 할 수있는 기능입니다. 실제 가치).

물론 단점은 이제 프로그램에서 사용하는 모든 클래스에 대해 알고있는 메가 기능이 하나 있다는 것입니다. 그것이 DI 프레임 워크가 도울 수있는 것입니다. 그러나이 접근법이 왜 가치가 있는지 이해하는 데 어려움이 있다면 먼저 수동 종속성 주입으로 시작하는 것이 좋습니다. 따라서 다양한 프레임 워크가 당신을 위해 무엇을 할 수 있는지 더 잘 이해할 수 있습니다.


7
왜 첫 번째 코드보다 두 번째 코드를 선호합니까? 첫 번째 키워드에는 새로운 키워드 만 있습니다. 어떻게 도움이 되나요?
user962206

17
@ user962206 B
jk와

66
@ user962206은 또한 B가 생성자에 일부 매개 변수가 필요한 경우 어떻게 될지 생각해보십시오. 인스턴스를 인스턴스화하려면 A는 A와 완전히 관련이없는 매개 변수에 대해 알아야합니다 (B에 의존하고 싶어합니다) , B가 무엇에 의존하지 않는가). 이미 구성된 B (또는 그 문제에 대한 B의 서브 클래스 또는 모의)를 A의 생성자에 전달하면 A가 B에만 의존하게됩니다 :)
epidemian

17
@ acidzombie24 : 많은 디자인 패턴과 마찬가지로 DI는 간단한 접근 방식이 문제가 될만큼 코드베이스가 충분히 크지 않으면 실제로 유용하지 않습니다. 내 생각에는 DI가 실제로 20,000 줄 이상의 코드 라인 및 / 또는 다른 라이브러리 또는 프레임 워크에 대한 20 개 이상의 종속성을 가질 때까지 실제로 개선되지 않을 것입니다. 응용 프로그램이 그보다 작 으면 DI 스타일로 프로그래밍하는 것을 선호 할 수 있지만 그 차이는 그리 크지 않습니다.
Daniel Pryden

2
@DanielPryden 코드 크기가 동적 인 것만 큼 중요하지 않다고 생각합니다. 동일한 인터페이스에 맞는 새 모듈을 정기적으로 추가하는 경우 종속 코드를 자주 변경할 필요가 없습니다.
FistOfFury

35

다른 답변에서 언급했듯이 종속성 주입은 종속성 주입을 사용하는 클래스 외부에서 종속성을 만드는 방법입니다. 당신은 외부에서 그것들을 주입하고, 수업의 내부에서 그들의 창조물에 대한 통제권을 가지십시오. 이것이 의존성 주입이 IoC ( Inversion of Control ) 원칙을 실현 한 이유이기도 합니다.

DI가 패턴 인 IoC가 원칙입니다. 내 경험이 진행되는 한 "하나 이상의 로거가 필요"할 수있는 이유는 실제로 충족되지 않지만 실제로는 무언가를 테스트 할 때마다 실제로 필요하기 때문입니다. 예를 들면 :

내 기능 :

쿠폰을 볼 때 자동으로 확인했음을 표시하여 잊지 않도록합니다.

이것을 다음과 같이 테스트 할 수 있습니다.

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

따라서 어딘가에 OfferWeasel다음과 같은 오퍼 객체를 빌드합니다.

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

여기서 문제 DateTime.Now는 테스트 코드를 입력하더라도 몇 밀리 초 정도 꺼질 수 있기 때문에 설정 한 날짜가 주장 된 날짜와 다르기 때문에이 테스트는 항상 실패한다는 것입니다. 항상 실패합니다. 더 좋은 해결책은 이제이를 위해 인터페이스를 만드는 것입니다.이 인터페이스를 사용하면 몇시에 설정할지 제어 할 수 있습니다.

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

인터페이스는 추상화입니다. 하나는 진짜이고 다른 하나는 필요한 곳에 시간을 허비 할 수 있습니다. 그런 다음 테스트를 다음과 같이 변경할 수 있습니다.

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

이와 같이 종속성을 주입하여 (현재 시간을 가져옴) "제어 반전"원칙을 적용했습니다. 이 작업을 수행하는 주된 이유는 분리 된 단위 테스트를보다 쉽게하기 위해 다른 방법이 있기 때문입니다. 예를 들어 C # 함수에서 변수로 전달 될 수 있으므로 인터페이스와 클래스는 필요하지 않으므로 인터페이스 대신 Func<DateTime> 를 하여 동일한 결과를 얻을 . 또는 동적 접근 방식을 취하면 동등한 방법을 사용하는 객체 ( duck typing )를 전달하면 인터페이스가 전혀 필요하지 않습니다.

둘 이상의 로거가 거의 필요하지 않습니다. 그럼에도 불구하고 Java 또는 C #과 같은 정적으로 유형이 지정된 코드에는 종속성 주입이 필수적입니다.

과... 모든 종속 항목을 사용할 수있는 경우 런타임에 객체의 목적을 올바르게 수행 할 수 있으므로 속성 삽입을 설정하는 데 많이 사용되지 않습니다. 제 생각에는 생성자가 호출 될 때 모든 종속성이 충족되어야하므로 생성자 주입이 필요합니다.

도움이 되었기를 바랍니다.


4
그것은 실제로 끔찍한 해결책처럼 보입니다. 나는 Daniel Pryden 답변이 제안한 것과 같은 코드를 확실히 작성 하지만 특정 단위 테스트의 경우 DateTime을 수행하고 함수 전후에 시간이 있는지 확인하십시오. 더 많은 인터페이스를 추가하거나 더 많은 코드 줄을 추가하는 것은 나에게 나쁜 생각처럼 보입니다.

3
나는 일반적인 A (B) 예제를 좋아하지 않으며 로거가 100 개의 구현을해야한다고 생각하지 않았습니다. 이것은 최근에 발생 한 예이며 실제로 PostSharp를 사용하여 포함시킨 5 가지 방법 중 하나입니다. 고전적인 클래스 기반 ctor 주입 방식을 보여줍니다. DI를 잘 활용 한 실제 사례를 더 잘 설명해 주시겠습니까?
cessor

2
나는 DI를 잘 사용하는 것을 본 적이 없다. 그래서 제가 질문을 썼습니다.

2
도움이되지 않았습니다. 내 코드는 항상 테스트하기 쉽습니다. DI는 코드가 잘못된 큰 코드 기반에 적합합니다.

1
작은 기능의 프로그램에서도 f (x), g (f)를 가질 때마다 이미 의존성 주입을 사용하고 있으므로 JS의 각 연속은 의존성 주입으로 계산됩니다. 내 추측은 당신이 이미 그것을 사용하고 있다는 것입니다;)
cessor

15

고전적인 대답은 더 분리 된 응용 프로그램을 만드는 것입니다.이 응용 프로그램은 런타임 중에 어떤 구현이 사용 될지 알지 못합니다.

예를 들어, Google은 전 세계의 많은 결제 제공 업체와 협력하는 중앙 결제 제공 업체입니다. 그러나 요청이있을 때 어떤 결제 프로세서를 호출할지 모릅니다. 다음과 같은 수많은 스위치 케이스로 하나의 클래스를 프로그래밍 할 수 있습니다.

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

이제이 코드가 제대로 분리되지 않았기 때문에이 코드를 모두 단일 클래스로 유지해야한다고 상상해보십시오. 지원할 모든 새 프로세서에 대해 새로운 if // switch case를 만들어야한다고 상상할 수 있습니다 그러나 모든 방법에서 Dependency Injection (또는 Inversion of Control-때로는 호출되기 때문에 프로그램 실행을 제어하는 ​​사람은 런타임에만 알고 합병증은 아님)을 사용하면 더 복잡해집니다. 매우 깔끔하고 유지 보수가 쉽습니다.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

** 코드가 컴파일되지 않습니다.


가상 메소드 / 인터페이스를 사용하는 곳에서 필요하다고 말하는 것처럼 들리기 때문에 +1입니다. 그러나 그것은 여전히 ​​드물다. 나는 아직도 그것을 new ThatProcessor()대신 에 전달한 다음 프레임 워크를 사용한다

@ItaiS 클래스 팩토리 디자인 패턴으로 수많은 스위치를 피할 수 있습니다. 리플렉션 사용 System.Reflection.Assembly.GetExecutingAssembly (). CreateInstance ()
domenicr

@domenicr 물론입니다! 하지만 간단한 예를 들어 설명하고 싶었습니다
Itai Sagi

팩토리 클래스의 필요성을 제외하고 위의 설명에 동의합니다. 우리가 팩토리 클래스를 구현하는 순간 단순히 조잡한 선택입니다. Bruce Erkel의 Poymorphism 및 가상 기능 장에서 찾은 위의 가장 좋은 설명. 실제 DI는 선택이 없어야하며 런타임에 인터페이스를 통해 객체 유형이 자동으로 결정되어야합니다. 그것은 또한 진정한 다형성 행동입니다.
Arvind Krmar

예를 들어 (c ++에 따라) 기본 클래스에 대한 참조를 취하고 선택하지 않고 파생 클래스의 동작을 구현하는 공통 인터페이스가 있습니다. void tune (Instrument & i) {i.play (middleC); } int main () {바람 플루트; 조율 (피리); } 악기는 기본 클래스이며 바람은 그 클래스에서 파생됩니다. c ++에 따르면 가상 함수는 공통 인터페이스를 통해 파생 클래스의 동작을 구현할 수 있습니다.
Arvind Krmar 12

6

DI를 사용하는 주된 이유는 지식이있는 구현 지식에 대한 책임을지고 싶어하기 때문입니다. DI의 아이디어는 인터페이스에 의한 캡슐화 및 디자인과 매우 일치합니다. 프런트 엔드가 백 엔드에서 일부 데이터를 요청하면 백 엔드가 해당 질문을 해결하는 방법은 프런트 엔드에 중요하지 않습니다. 그것은 requesthandler에 달려 있습니다.

그것은 OOP에서 이미 오랫동안 일반적입니다. 여러 번 다음과 같은 코드 조각을 만듭니다.

I_Dosomething x = new Impl_Dosomething();

단점은 구현 클래스가 여전히 하드 코딩되어 있으므로 프런트 엔드에 구현이 사용되는 지식이 있다는 것입니다. DI는 인터페이스의 디자인을 한 단계 더 발전시켜 프론트 엔드가 알아야 할 것은 인터페이스에 대한 지식뿐입니다. DYI와 DI 사이에는 서비스 로케이터의 패턴이 있습니다. 프런트 엔드는 요청을 해결할 수 있도록 키 (서비스 로케이터의 레지스트리에 있음)를 제공해야하기 때문입니다. 서비스 로케이터 예 :

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

DI 예 :

I_Dosomething x = DIContainer.returnThat();

DI의 요구 사항 중 하나는 컨테이너가 어떤 클래스가 어떤 인터페이스의 구현인지 알아낼 수 있어야한다는 것입니다. 따라서 DI 컨테이너에는 강력한 형식의 디자인과 각 인터페이스에 대해 한 번의 구현 만 필요합니다. 계산기와 같은 인터페이스를 동시에 더 구현해야하는 경우 서비스 로케이터 또는 팩토리 디자인 패턴이 필요합니다.

D (b) I : 인터페이스에 의한 의존성 주입과 디자인. 그러나 이러한 제한은 실용적이지 못합니다. D (b) I를 사용하면 클라이언트와 공급자 간의 통신이 가능하다는 이점이 있습니다. 인터페이스는 객체 또는 일련의 동작에 대한 관점입니다. 후자는 여기서 중요합니다.

코딩에서 D (b) I와 함께 서비스 계약 관리를 선호합니다. 그들은 함께 가야합니다. 서비스 계약을 조직적으로 관리하지 않고 기술 솔루션으로 D (b) I를 사용하는 것은 DI가 캡슐화의 추가 계층이기 때문에 필자의 관점에서 그다지 유익하지 않습니다. 그러나 조직 관리와 함께 사용할 수 있으면 D (b) I가 제공하는 조직 원칙을 실제로 활용할 수 있습니다. 장기적으로 테스트, 버전 관리 및 대안 개발과 같은 주제로 고객 및 기타 기술 부서와의 커뮤니케이션을 구조화하는 데 도움이 될 수 있습니다. 하드 코딩 된 클래스와 같이 암시 적 인터페이스가 있으면 시간이 지남에 따라 D (b) I를 사용하여 명시 적으로 만들면 통신이 훨씬 덜합니다. 그것은 모두 시간이 지남에 따라 유지 보수로 귀결됩니다. :-)


1
"결점은 구현 클래스가 여전히 하드 코딩되어 있다는 것입니다."<-대부분의 경우 구현은 하나 뿐이며 이미 (.NET에 내장되지 않은 인터페이스가 필요한 게임 이외의 코드는 생각할 수 없습니다) ).

@ acidzombie24 아마도 ...하지만 인터페이스가 필요한 경우 처음부터 DI를 사용하여 솔루션을 구현하려는 노력과 비 DI 솔루션을 변경하려는 노력을 비교하십시오. 나는 거의 항상 첫 번째 옵션을 사용합니다. 내일 100.000 $를 지불하는 대신 100 $를 지불하는 것이 좋습니다.
Golo Roden

1
@GoloRoden 실제로, 유지 관리는 D (b) I와 같은 기술을 사용하는 주요 문제입니다. 이는 응용 프로그램 비용의 80 %입니다. 처음부터 인터페이스를 사용하여 필요한 동작을 명시 적으로 디자인하면 나중에 많은 시간과 비용을 절약 할 수 있습니다.
Loek Bergman

지금까지 나는 $ 0를 지불했고 지금까지는 여전히 $ 0 만 지불하면되기 때문에 지불해야 할 때까지 진정으로 이해하지 않을 것입니다. 그러나 나는 모든 라인이나 기능을 깨끗하게 유지하기 위해 $ 0.05를 지불합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.