먼저이 답변에 대한 가정을 설명하고 싶습니다. 항상 사실은 아니지만 매우 자주 :
인터페이스는 형용사입니다. 수업은 명사입니다.
(실제로 명사 인 인터페이스가 있지만 여기서는 일반화하고 싶습니다.)
따라서, 인터페이스를 예하는 등 뭔가 할 수있다 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 프레임 워크는 또한 서비스 로케이터를 제공합니다.
그러나 기본적으로 그게 다입니다.
추신 : 여기에 설명 한 것은 생성자 주입 이라는 기술입니다. 생성자 매개 변수가 아닌 속성 주입 도 있지만 속성은 종속성을 정의하고 해결하는 데 사용됩니다. 속성 삽입은 선택적 종속성으로, 생성자 삽입은 필수 종속성으로 생각하십시오. 그러나 이에 대한 논의는이 질문의 범위를 벗어납니다.