컨테이너에 의존성 주입을 사용하는 것과 서비스 로케이터를 사용하는 것의 차이점은 무엇입니까?


107

클래스 내에서 직접 종속성을 인스턴스화하는 것은 나쁜 습관으로 간주됩니다. 이렇게하면 모든 것이 단단히 결합되어 테스트가 매우 어려워집니다.

내가 본 거의 모든 프레임 워크는 서비스 로케이터를 사용하는 것보다 컨테이너를 사용하여 종속성 주입을 선호하는 것으로 보입니다. 두 클래스 모두 클래스가 의존성을 요구할 때 어떤 객체를 반환해야하는지 프로그래머가 지정할 수있게함으로써 동일한 결과를 얻는 것처럼 보입니다.

둘의 차이점은 무엇입니까? 왜 다른 것을 선택해야합니까?


3
이것은 StackOverflow에서 질문되고 답변되었습니다 : stackoverflow.com/questions/8900710/…
Kyralessa

2
제 생각에 개발자가 다른 사람들이 자신의 서비스 로케이터가 의존성 주입이라고 생각하도록 속이는 것은 드문 일이 아닙니다. 의존성 주입은 종종 더 진보 된 것으로 생각되기 때문에 그렇게합니다.
Gherman


1
서비스 로케이터를 사용하면 일시적인 리소스가 언제 파괴 될 수 있는지 알기가 더 어렵다고 언급 한 사람이 아무도 없습니다. 나는 항상 그것이 주된 이유라고 생각했습니다.
Buh Buh

답변:


126

객체 자체가 생성자를 통해 객체를 수락하는 것이 아니라 의존성을 요청하는 경우 몇 가지 필수 정보가 숨겨져 있습니다. new의존성을 인스턴스화하기 위해 사용하는 매우 밀접하게 결합 된 경우보다 약간 더 좋습니다 . 실제로 얻는 종속성을 변경할 수 있기 때문에 커플 링이 줄어 들지만 여전히 흔들릴 수없는 종속성이 있습니다 : 서비스 로케이터. 그것은 모든 것이 의존하는 것이됩니다.

생성자 인수를 통해 종속성을 제공하는 컨테이너가 가장 명확합니다. 우리는 객체에 AccountRepositorya와 a 가 모두 필요하다는 것을 바로 알 수 PasswordStrengthEvaluator있습니다. 서비스 로케이터를 사용할 때 해당 정보는 즉시 명확하지 않습니다. 객체에 17 개의 의존성이있는 경우를 바로보고, "흠, 그게 많은 것 같아요. 무슨 일이 벌어지고 있습니까?" 서비스 로케이터에 대한 호출은 다양한 방법으로 확산 될 수 있으며 조건부 논리 뒤에 숨어있을 수 있으며, 모든 것을 수행하는 "하나님 클래스"를 만들지 않았다는 사실을 모를 수도 있습니다. 어쩌면 그 클래스는 더 집중된 3 개의 작은 클래스로 리팩토링 될 수 있으므로 테스트 할 수 있습니다.

이제 테스트를 고려하십시오. 객체가 서비스 로케이터를 사용하여 종속성을 얻는 경우 테스트 프레임 워크에도 서비스 로케이터가 필요합니다. 테스트에서는 테스트 할 객체에 종속성을 제공하도록 서비스 로케이터를 구성하고 (아마도 a FakeAccountRepository및 a VeryForgivingPasswordStrengthEvaluator) 테스트를 실행합니다. 그러나 그것은 객체의 생성자에 의존성을 지정하는 것보다 더 효과적입니다. 또한 테스트 프레임 워크는 서비스 로케이터에 의존합니다. 모든 테스트에서 구성해야 할 또 다른 사항은 테스트 작성의 매력을 떨어 뜨립니다.

Mark Seeman의 기사에서 "Serivce Locator는 안티 패턴"을 찾아보십시오. .Net 세계에 있다면 그의 책을 얻으십시오. 아주 좋습니다.


1
Gary McLean Hall의 C #통해 Adaptive Code 에 대해 생각한 질문을 읽으십시오 .이 답변과 잘 어울립니다. 그것은 서비스 로케이터와 같은 좋은 비유가 안전의 열쇠입니다. 수업에 참여하면 찾기 쉽지 않은 의존성을 만들 수 있습니다.
데니스

10
한 가지는이 IMO에 추가 될 필요가 constructor supplied dependenciesservice locator이전 한 것입니다 수 있습니다 후자는 수 있지만, 컴파일 시간을 verfied 에만 런타임을 확인.
andras

2
또한 서비스 로케이터는 구성 요소에 많은 종속성이없는 것을 권장하지 않습니다. 생성자에 10 개의 매개 변수를 추가 해야하는 경우 코드에 문제가 있습니다. 그러나 서비스 로케이터에 대해 10 번의 정적 호출을하는 경우 어떤 종류의 문제가 있는지 쉽게 알 수 없습니다. 서비스 로케이터로 큰 프로젝트를 수행했으며 이것이 가장 큰 문제였습니다. 앉고, 반영하고, 재 설계하고, 리팩토링하는 것보다 새로운 길을 단락시키는 것은 너무 쉬운 방법이었습니다.
페이스

1
테스트 정보- But that's more work than specifying dependencies in the object's constructor.반대하고 싶습니다. 서비스 로케이터를 사용하면 실제로 테스트에 필요한 3 가지 종속성 만 지정하면됩니다. 생성자 기반 DI를 사용하면 7을 사용하지 않더라도 10을 모두 지정해야합니다.
Vilx-

4
@ Vilx- 사용되지 않는 생성자 매개 변수가 여러 개 있으면 클래스가 단일 책임 원칙을 위반하는 것입니다.
RB.

79

당신이 공장에서 신발 을 만드는 노동자라고 상상해보십시오 .

신발 을 조립 해야 할 책임이 있으므로 그렇게하려면 많은 일이 필요합니다.

  • 가죽
  • 측정 테이프
  • 접착제
  • 손톱
  • 망치
  • 가위
  • 신발 끈

등등.

공장에서 일하고 있으며 시작할 준비가되었습니다. 진행 방법에 대한 지침 목록이 있지만 아직 재료 나 도구가 없습니다.

서비스 로케이터는 당신이 필요로하는 것을 얻을 도움을 줄 수있는 감독과 같다.

당신은 무언가를 필요할 때마다 서비스 로케이터에게 요청하고, 그들은 당신을 위해 그것을 찾기 위해 떠납니다. 서비스 로케이터는 요청한 내용과 찾는 방법에 대해 미리 알려주었습니다.

그러나 예기치 않은 것을 요구하지 않기를 바랍니다. 로케이터에게 특정 도구 나 재료에 대한 정보를 미리 알려주지 않은 경우, 해당 도구 나 재료를 얻을 수 없으며, 어깨를 으 will 할 것입니다.

서비스 로케이터

의존성 주입 (DI) 컨테이너는 모두가 하루의 시작 부분에서 필요로하는 모든 것을 채워집니다 큰 상자와 같다.

팩토리가 시작되면 컴포지션 루트 로 알려진 Big Boss 가 컨테이너를 잡고 모든 것을 라인 관리자에게 전달 합니다.

라인 관리자는 이제 당일 업무를 수행하는 데 필요한 것을 갖습니다. 그들은 가지고있는 것을 가지고 부하에게 필요한 것을 전달합니다.

이 프로세스는 종속성이 생산 라인을 속여서 계속됩니다. 결국 포먼을 위해 재료와 도구 컨테이너가 나타납니다.

귀하의 포먼은 이제 귀하가 요구하지 않아도 귀하와 다른 근로자에게 필요한 것을 정확하게 배포합니다.

기본적으로, 출근하자마자 필요한 모든 것이 이미 상자에 있습니다. 당신은 그들을 얻는 방법에 대해 아무것도 몰랐습니다.

의존성 주입 컨테이너


45
이것은이 두 가지 각각이 무엇인지에 대한 훌륭한 설명입니다. 그러나 "왜 하나를 선택해야합니까?"라는 질문에 대답하지 않습니다.
Matthieu M.

21
"예기치 않은 것을 요구하지 않는 것이 좋습니다. 특정 도구 나 재료에 대해 로케이터에게 미리 알려주지 않았다면"DI 컨테이너에서도 마찬가지입니다.
Kenneth K.

5
@FrankHopkins DI 컨테이너에 인터페이스 / 클래스 등록을 생략하면 코드가 여전히 컴파일되어 런타임에 즉시 실패합니다.
Kenneth K.

3
설정 방법에 따라 둘 다 실패 할 가능성이 높습니다. 그러나 DI 컨테이너를 사용하면 클래스 X가 실제로 하나의 종속성을 필요로 할 때 나중에가 아니라 클래스 X가 생성 될 때 더 빨리 문제에 부딪 칠 수 있습니다. 문제가 발생하여 찾아서 해결하기가 더 쉽기 때문에 논란의 여지가 더 낫습니다. 그러나 Kenneth에 동의합니다.이 답변은이 문제가 서비스 로케이터에만 존재한다는 것을 암시합니다.
GolezTrol

3
대단한 대답이지만, 나는 그것이 다른 것을 그리워한다고 생각합니다. 당신이 코끼리에 대한 모든 단계에서 주임에게, 그가 경우 즉, 않습니다 하나를 얻을하는 방법을 알고 그가 당신이 그것을 필요로하지 않더라도 어떠한 질문 asked-없는 당신을 위해 하나를 얻을 것이다. 그리고 바닥에서 문제를 디버깅하려고하는 사람 (당신이 개발자라면)은 wtf 가이 책상에서 여기에서하는 코끼리라고 궁금해 할 것입니다. DI를 사용하는 작업자 클래스 는 작업을 시작하기 전에 필요한 모든 종속성을 미리 선언 하므로 추론하기가 더 쉽습니다.
Stephen Byrne

10

웹을 검색 할 때 발견 한 몇 가지 추가 사항 :

  • 생성자에 의존성을 주입하면 클래스가 무엇을 원하는지 쉽게 이해할 수 있습니다. 최신 IDE는 생성자가 허용하는 인수와 해당 유형을 암시합니다. 서비스 로케이터를 사용하는 경우 필요한 종속성을 알기 전에 클래스를 읽어야합니다.
  • 의존성 주입은 서비스 로케이터보다 "문의하지 말 것"원칙을 따르는 것 같습니다. 종속성이 특정 유형이어야한다는 것을 요구함으로써 어떤 종속성이 필요한지 "알고"있습니다. 필요한 종속성을 전달하지 않고 클래스를 인스턴스화하는 것은 불가능합니다. 서비스 로케이터를 사용하면 서비스를 "요청"하고 서비스 로케이터가 올바르게 구성되지 않으면 필요한 것을 얻지 못할 수 있습니다.

4

이 파티에 늦었지만 저항 할 수 없습니다.

컨테이너에 의존성 주입을 사용하는 것과 서비스 로케이터를 사용하는 것의 차이점은 무엇입니까?

때로는 전혀 없습니다. 차이를 만드는 것은 무엇에 대해 아는 것입니다.

의존성을 찾는 클라이언트가 컨테이너에 대해 알고있을 때 서비스 로케이터를 사용하고 있다는 것을 알고 있습니다. 컨테이너를 통해 가져 와서 의존성을 찾는 방법을 아는 클라이언트는 서비스 로케이터 패턴입니다.

서비스 로케이터를 피하려면 컨테이너를 사용할 수 없습니까? 아니요. 고객이 컨테이너에 대해 알지 못하게해야합니다. 주요 차이점은 컨테이너를 사용하는 위치입니다.

Client필요 하다고 말할 수 Dependency있습니다. 컨테이너에는가 있습니다 Dependency.

class Client { 
    Client() { 
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        this.dependency = (Dependency) beanfactory.getBean("dependency");        
    }
    Dependency dependency;
}

우리는 방금 Client찾는 방법을 알고 있기 때문에 서비스 로케이터 패턴을 따랐다 Dependency. 물론 하드 코딩 된 코드를 사용 ClassPathXmlApplicationContext하지만 Client호출 때문에 여전히 서비스 로케이터가 있다고 주입하더라도 beanfactory.getBean().

서비스 로케이터를 피하기 위해이 컨테이너를 버릴 필요는 없습니다. 당신은 그것을 밖으로 이동해야 Client하므로 Client그것에 대해 알지 못합니다.

class EntryPoint { 
    public static void main(String[] args) {
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        Client client = (Client) beanfactory.getBean("client");

        client.start();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dependency" class="Dependency">
    </bean>

    <bean id="client" class="Client">
        <constructor-arg value="dependency" />        
    </bean>
</beans>

Client컨테이너가 어떻게 존재 하는지 전혀 알지 못합니다.

class Client { 
    Client(Dependency dependency) { 

        this.dependency = dependency;        
    }
    Dependency dependency;
}

컨테이너를 모든 클라이언트 밖으로 옮기고 오래 지속되는 모든 객체의 객체 그래프를 만들 수있는 메인에 고정합니다. 추출 할 객체 중 하나를 선택하고 메소드를 호출하면 전체 그래프 표시가 시작됩니다.

이렇게하면 모든 정적 구성이 컨테이너 XML로 이동하면서 모든 클라이언트가 의존성을 찾는 방법을 행복하게 알 수 없습니다.

그러나 main은 여전히 ​​의존성을 찾는 방법을 알고 있습니다! 그렇습니다. 그러나 그 지식을 주변에 퍼 뜨리지 않으면 서비스 로케이터의 핵심 문제를 피할 수 있습니다. 컨테이너 사용 결정은 이제 한곳에서 이루어지며 수백 명의 클라이언트를 다시 작성하지 않고도 변경할 수 있습니다.


1

두 컨테이너의 차이점을 이해하는 가장 쉬운 방법과 DI 컨테이너가 서비스 로케이터보다 훨씬 나은 이유는 왜 의존성 반전을 먼저 수행하는지에 대한 생각입니다.

우리는 의존성 역전을 수행하여 각 클래스가 연산에 의존하는 것을 정확하게 명시 합니다. 우리가 달성 할 수있는 가장 느슨한 결합을 생성하기 때문에 그렇게합니다. 커플 링이 느슨할수록 테스트 및 리팩토링이 쉬워집니다 (일반적으로 코드가 더 깨끗하기 때문에 향후 리팩토링이 가장 적게 필요함).

다음 클래스를 보자.

public class MySpecialStringWriter
{
  private readonly IOutputProvider outputProvider;
  public MySpecialFormatter(IOutputProvider outputProvider)
  {
    this.outputProvider = outputProvider;
  }

  public void OutputString(string source)
  {
    this.outputProvider.Output("This is the string that was passed: " + source);
  }
}

이 클래스에서는 IOutputProvider가 필요하며이 클래스를 작동시키기 위해 다른 것은 필요 없다고 명시 적으로 설명하고 있습니다. 이것은 완전히 테스트 가능 하며 단일 인터페이스에 의존합니다. 이 클래스를 다른 프로젝트를 포함하여 응용 프로그램의 어느 곳으로나 이동할 수 있으며 필요한 것은 IOutputProvider 인터페이스에 대한 액세스입니다. 다른 개발자가이 클래스에 새로운 것을 추가하고 싶을 때, 두 번째 의존성 이 필요한 경우, 생성자에 무엇이 필요한지 명시 해야합니다.

서비스 로케이터로 동일한 클래스를 살펴보십시오.

public class MySpecialStringWriter
{
  private readonly ServiceLocator serviceLocator;
  public MySpecialFormatter(ServiceLocator serviceLocator)
  {
    this.serviceLocator = serviceLocator;
  }

  public void OutputString(string source)
  {
    this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

이제 서비스 로케이터를 종속성으로 추가했습니다. 즉시 명백한 문제는 다음과 같습니다.

  • 이것의 첫 번째 문제 는 동일한 결과를 달성하기 위해 더 많은 코드필요 하다는 입니다. 더 많은 코드가 잘못되었습니다. 훨씬 더 많은 코드는 아니지만 여전히 더 많은 코드입니다.
  • 두 번째 문제는 내 의존성이 더 이상 명백하지 않다는 것 입니다. 여전히 수업에 무언가를 주입해야합니다. 내가 원하는 것은 분명하지 않다. 내가 요청한 것의 속성에 숨겨져 있습니다. 클래스를 다른 어셈블리로 옮기려면 ServiceLocator와 IOutputProvider에 모두 액세스해야합니다.
  • 세 번째 문제는 클래스에 코드를 추가 할 때 자신이 취하는 것을 깨닫지 못하는 다른 개발자 가 추가 종속성을 얻을 수 있다는 것 입니다.
  • 마지막으로,이 코드는 IOutputProvider 대신 ServiceLocator와 IOutputProvider를 조롱해야하기 때문에 ServiceLocator가 인터페이스 인 경우에도 테스트하기더 어렵 습니다.

그렇다면 서비스 로케이터를 정적 클래스로 만들지 않는 이유는 무엇입니까? 한 번 보자:

public class MySpecialStringWriter
{
  public void OutputString(string source)
  {
    ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

이것은 훨씬 더 간단합니다.

잘못된.

IOutputProvider는 전 세계 15 개의 서로 다른 데이터베이스에 문자열을 작성하고 완료하는 데 시간이 오래 걸리는 매우 오래 실행되는 웹 서비스로 구현되었다고 가정 해 보겠습니다.

이 수업을 테스트 해 봅시다. 테스트를 위해 다른 IOutputProvider 구현이 필요합니다. 시험은 어떻게 작성합니까?

테스트를 위해 호출 될 때 IOutputProvider의 다른 구현을 사용하려면 정적 ServiceLocator 클래스에서 멋진 구성을 수행해야합니다. 그 문장을 쓰는 것조차도 고통 스러웠습니다. 그것을 구현하는 것은 끔찍하고 유지 보수의 악몽 이 될 것 입니다 . 테스트 할 클래스, 특히 해당 클래스가 실제로 테스트하려는 클래스가 아닌 경우 클래스를 수정할 필요가 없습니다.

이제 a) 관련이없는 ServiceLocator 클래스에서 눈에 띄지 않는 코드 변경을 유발하는 테스트; 또는 b) 전혀 테스트하지 않습니다. 유연성이 떨어지는 솔루션도 있습니다.

따라서 서비스 로케이터 클래스 생성자에 주입되어야합니다. 즉, 앞에서 언급 한 특정 문제가 남아 있습니다. 서비스 로케이터는 더 많은 코드를 필요로하고, 다른 개발자에게 필요하지 않은 것을 필요로한다고 말하고, 다른 개발자가 더 나쁜 코드를 작성하도록 장려하며, 유연성이 떨어집니다.

간단히 말하면 서비스 로케이터는 응용 프로그램에서 커플 링을 증가 하고 매우 결합 된 코드를 작성하는 다른 개발자를 권장합니다 .


SL (Service Locator)과 DI (Dependency Injection)는 모두 같은 방식으로 동일한 문제를 해결합니다. DI에 "telling"DI 또는 "SL"을 요청하는 경우 유일한 차이점입니다.
매튜 화이트

@MatthewWhited 가장 큰 차이점은 서비스 로케이터를 사용하여 암시 적 종속성의 수입니다. 그리고 이것은 코드의 장기적인 유지 관리 성과 안정성에 큰 차이를 만듭니다.
스티븐

정말 아니에요 어느 쪽이든 같은 수의 종속성이 있습니다.
매튜 화이트

처음에는 그렇습니다. 그러나 의존성을 취하는 것을 깨닫지 않고 서비스 로케이터로 의존성을 취할 수 있습니다. 우선 우리가 의존성 역전을 수행하는 이유의 많은 부분을 물리칩니다. 한 클래스는 속성 A와 B에 의존하고 다른 클래스는 B와 C에 의존합니다. 갑자기 서비스 로케이터는 신 클래스가됩니다. DI 컨테이너는 그렇지 않으며 두 클래스는 각각 A와 B 및 B와 C에만 올바르게 의존합니다. 이를 통해 리팩토링이 100 배 더 쉬워집니다. 서비스 로케이터는 DI와 동일하게 보이지만 그렇지 않다는 점에서 교활합니다.
스티븐
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.