의존성 주입 (DI) 을 이해하려고하는데 다시 실패했습니다. 어리석은 것 같습니다. 내 코드는 결코 엉망이 아닙니다. 나는 가상 함수와 인터페이스를 거의 작성하지 않으며 (푸른 달에 한 번만 사용하더라도) 모든 구성은 json.net (때로는 XML 직렬 변환기 사용)을 사용하여 마술처럼 클래스로 직렬화됩니다.
어떤 문제가 해결되는지 잘 모르겠습니다. “hi.이 함수를 실행하면이 유형의 객체를 반환하고 이러한 매개 변수 / 데이터를 사용합니다.”
하지만 … 왜 내가 그걸 사용 하겠어? 참고로 사용할 필요는 없지만 object
그 목적이 무엇인지 이해합니다.
DI를 사용하는 웹 사이트 또는 데스크톱 응용 프로그램을 구축 할 때 실제 상황은 무엇입니까? 누군가가 게임에서 인터페이스 / 가상 기능을 사용하려는 이유에 대한 사례를 쉽게 생각 해낼 수 있지만, 게임 이외의 코드에서 사용하는 것은 매우 드 (니다 (단일 인스턴스를 기억할 수 없을 정도로 드 ra니다).
답변
먼저이 답변에 대한 가정을 설명하고 싶습니다. 항상 사실은 아니지만 매우 자주 :
인터페이스는 형용사입니다. 수업은 명사입니다.
(실제로 명사 인 인터페이스가 있지만 여기서는 일반화하고 싶습니다.)
따라서, 인터페이스를 예하는 등 뭔가 할 수있다 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 프레임 워크는 또한 서비스 로케이터를 제공합니다.
그러나 기본적으로 그게 다입니다.
추신 : 여기에 설명 한 것은 생성자 주입 이라는 기술입니다. 생성자 매개 변수가 아닌 속성 주입 도 있지만 속성은 종속성을 정의하고 해결하는 데 사용됩니다. 속성 삽입은 선택적 종속성으로, 생성자 삽입은 필수 종속성으로 생각하십시오. 그러나 이에 대한 논의는이 질문의 범위를 벗어납니다.
답변
나는 사람들이 의존성 주입 과 의존성 주입 프레임 워크 (또는 종종 컨테이너 라고도 함) 의 차이점에 대해 많은 혼란을 겪고 있다고 생각합니다 .
의존성 주입은 매우 간단한 개념입니다. 이 코드 대신 :
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 프레임 워크가 도울 수있는 것입니다. 그러나이 접근법이 왜 가치가 있는지 이해하는 데 어려움이 있다면 먼저 수동 종속성 주입으로 시작하는 것이 좋습니다. 따라서 다양한 프레임 워크가 당신을 위해 무엇을 할 수 있는지 더 잘 이해할 수 있습니다.
답변
다른 답변에서 언급했듯이 종속성 주입은 종속성 주입을 사용하는 클래스 외부에서 종속성을 만드는 방법입니다. 당신은 외부에서 그것들을 주입하고, 수업의 내부에서 그들의 창조물에 대한 통제권을 가지십시오. 이것이 의존성 주입이 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 #과 같은 정적으로 유형이 지정된 코드에는 종속성 주입이 필수적입니다.
과…
모든 종속 항목을 사용할 수있는 경우 런타임에 객체의 목적을 올바르게 수행 할 수 있으므로 속성 삽입을 설정하는 데 많이 사용되지 않습니다. 제 생각에는 생성자가 호출 될 때 모든 종속성이 충족되어야하므로 생성자 주입이 필요합니다.
도움이 되었기를 바랍니다.
답변
고전적인 대답은 더 분리 된 응용 프로그램을 만드는 것입니다.이 응용 프로그램은 런타임 중에 어떤 구현이 사용 될지 알지 못합니다.
예를 들어, 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();
}
** 코드가 컴파일되지 않습니다.
답변
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를 사용하여 명시 적으로 만들면 통신이 훨씬 덜합니다. 그것은 모두 시간이 지남에 따라 유지 보수로 귀결됩니다. 🙂