간단한 예를 들어 보자. 아마도 로깅 수단을 주입하고있을 것이다.
수업 주입
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
나는 그것이 무슨 일이 일어나고 있는지 분명하다고 생각합니다. 또한 IoC 컨테이너를 사용하는 경우 명시 적으로 아무것도 주입하지 않아도 컴포지션 루트에 추가하기 만하면됩니다.
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
디버깅 할 때 Worker
개발자는 컴포지션 루트를 참조하여 사용중인 콘크리트 클래스를 결정하면됩니다.
개발자가 더 복잡한 논리를 필요로하는 경우 다음과 같이 작업 할 수있는 전체 인터페이스가 있습니다.
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
방법 주입
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
먼저 생성자 매개 변수의 이름이 더 길어졌습니다 methodThatLogs
. 해야 할 일을 알 수 없기 때문에이 작업이 필요합니다 Action<string>
. 인터페이스를 사용하면 완전히 명확 해졌지만 여기서는 매개 변수 이름 지정에 의존해야합니다. 이것은 빌드하는 동안 본질적으로 신뢰성이 떨어지고 시행하기가 더 어려워 보입니다.
이제이 방법을 어떻게 주입합니까? 글쎄, IoC 컨테이너는 당신을 위해 그것을하지 않을 것입니다. 따라서 인스턴스화 할 때 명시 적으로 주입합니다 Worker
. 이로 인해 몇 가지 문제가 발생합니다.
- 인스턴스화하는 것이 더 많은 일입니다.
Worker
- 디버깅
Worker
을 시도하는 개발자 는 구체적인 인스턴스가 무엇인지 파악하기가 더 어렵다는 것을 알게 될 것입니다. 그들은 단지 구성 루트를 상담 할 수 없습니다. 코드를 통해 추적해야합니다.
더 복잡한 논리가 필요한 경우는 어떻습니까? 귀하의 기술은 하나의 방법 만 노출합니다. 이제 복잡한 것들을 람다로 구울 수 있다고 가정합니다.
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
그러나 단위 테스트를 작성할 때 해당 람다 식을 어떻게 테스트합니까? 익명이므로 단위 테스트 프레임 워크에서 직접 인스턴스화 할 수 없습니다. 어쩌면 당신은 그것을 할 수있는 영리한 방법을 알아낼 수는 있지만 아마도 인터페이스를 사용하는 것보다 더 큰 PITA 일 것입니다.
차이점 요약 :
- 메소드 만 주입하면 목적을 추론하기가 더 어려우며 인터페이스는 목적을 명확하게 전달합니다.
- 메소드 만 주입하면 주입을받는 클래스에 기능이 덜 노출됩니다. 오늘 필요하지 않더라도 내일 필요할 수도 있습니다.
- IoC 컨테이너를 사용하는 메소드 만 자동으로 주입 할 수 없습니다.
- 컴포지션 루트에서 특정 인스턴스에서 어떤 구체적인 클래스가 작동하는지 알 수 없습니다.
- 람다 식 자체를 단위 테스트하는 것은 문제가됩니다.
위의 모든 내용이 정상이면 분석법 만 주입해도됩니다. 그렇지 않으면 전통을 고수하고 인터페이스를 주입하는 것이 좋습니다.