[design-patterns] ServiceLocator는 안티 패턴입니까?

최근 Service Locator 안티 패턴에 관한 Mark Seemann의 기사를 읽었습니다 .

필자는 ServiceLocator가 안티 패턴 인 두 가지 주요 이유를 지적합니다.

  1. API 사용법 문제 (완전히 괜찮습니다)
    클래스가 서비스 로케이터를 사용할 때 대부분의 경우 클래스에 PARAMETERLESS 생성자가 하나만 있으므로 종속성을 확인하기가 매우 어렵습니다. ServiceLocator와 달리 DI 방식은 생성자의 매개 변수를 통해 종속성을 명시 적으로 노출하므로 IntelliSense에서 종속성을 쉽게 확인할 수 있습니다.

  2. 유지 보수 문제 (나를 당혹스럽게한다)
    다음 expample을 고려하십시오

서비스 로케이터 접근 방식을 사용하는 ‘MyType’ 클래스가 있습니다 .

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

이제 ‘MyType’클래스에 다른 종속성을 추가하려고합니다

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

그리고 여기 내 오해가 시작됩니다. 저자는 말합니다 :

주요 변경 사항을 도입했는지 여부를 판단하기가 훨씬 어려워집니다. Service Locator가 사용되는 전체 애플리케이션을 이해해야하며 컴파일러가 도움을주지 않습니다.

그러나 DI 접근 방식을 사용하는 경우 잠시 기다립니다. 생성자 주입의 경우 생성자에 다른 매개 변수와의 종속성이 도입됩니다. 그리고 문제는 여전히있을 것입니다. ServiceLocator 설정을 잊어 버린 경우 IoC 컨테이너에 새 매핑을 추가하는 것을 잊어 버릴 수 있으며 DI 접근 방식에 동일한 런타임 문제가 발생합니다.

또한 저자는 단위 테스트 어려움에 대해 언급했습니다. 그러나 DI 접근법에 문제가 없을까요? 해당 클래스를 인스턴스화 한 모든 테스트를 업데이트해야합니까? 테스트를 컴파일 할 수 있도록 새로운 조롱 된 종속성을 전달하도록 업데이트합니다. 그리고 그 업데이트와 시간 소비로 인한 이점은 없습니다.

Service Locator 접근 방식을 방어하려고하지 않습니다. 그러나이 오해로 인해 나는 매우 중요한 것을 잃어 가고 있다고 생각합니다. 누군가 내 의심을 풀 수 있습니까?

업데이트 (요약) :

내 질문에 대한 대답은 “서비스 로케이터가 안티 패턴입니까?”는 실제로 상황에 따라 다릅니다. 그리고 나는 당신의 도구 목록에서 그것을 무시하지 않는 것이 좋습니다. 레거시 코드를 다루기 시작할 때 매우 유용 할 수 있습니다. 운 좋게도 프로젝트를 시작할 때 운이 좋으면 서비스 로케이터에 비해 몇 가지 장점이있는 DI 접근 방식이 더 나은 선택 일 수 있습니다.

그리고 새 프로젝트에 Service Locator를 사용하지 않을 것을 확신시킨 주요 차이점은 다음과 같습니다.

  • 가장 분명하고 중요한 : Service Locator는 클래스 종속성을 숨 깁니다.
  • 일부 IoC 컨테이너를 사용하는 경우 시작시 모든 생성자를 스캔하여 모든 종속성을 확인하고 누락 된 매핑 (또는 잘못된 구성)에 대한 즉각적인 피드백을 제공합니다. IoC 컨테이너를 서비스 로케이터로 사용하는 경우 불가능합니다

자세한 내용은 아래에 나와있는 훌륭한 답변을 읽으십시오.



답변

맞지 않는 상황이 있기 때문에 패턴을 앤티 패턴으로 정의하면, 앤티 패턴입니다. 그러나 이러한 추론으로 모든 패턴은 반 패턴이 될 것입니다.

대신 패턴의 올바른 사용법이 있는지 확인해야하며 Service Locator의 경우 몇 가지 사용 사례가 있습니다. 그러나 여러분이 제시 한 예를 살펴 보도록하겠습니다.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

해당 클래스의 유지 관리 악몽은 종속성이 숨겨져 있다는 것입니다. 해당 클래스를 작성하고 사용하는 경우 :

var myType = new MyType();
myType.MyMethod();

서비스 위치를 사용하여 숨겨진 경우 종속성이 있음을 이해하지 못합니다. 이제 의존성 주입을 사용한다면 :

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

종속성을 직접 발견하고 클래스를 만족시키기 전에 클래스를 사용할 수 없습니다.

일반적인 업무용 응용 프로그램에서는 바로 이러한 이유로 서비스 위치를 사용하지 않아야합니다. 다른 옵션이 없을 때 사용할 패턴이어야합니다.

패턴이 반 패턴입니까?

아니.

예를 들어, 제어 컨테이너의 반전은 서비스 위치가 없으면 작동하지 않습니다. 내부적으로 서비스를 해결하는 방법입니다.

그러나 훨씬 더 좋은 예는 ASP.NET MVC와 WebApi입니다. 컨트롤러에서 의존성 주입이 가능하다고 생각하는 것은 무엇입니까? 맞습니다-서비스 위치.

당신의 질문

그러나 DI 접근 방식을 사용하는 경우 잠시 기다립니다. 생성자 주입의 경우 생성자에 다른 매개 변수와의 종속성이 도입됩니다. 그리고 문제는 여전히있을 것입니다.

두 가지 더 심각한 문제가 있습니다.

  1. 서비스 위치를 사용하면 또 다른 종속성 인 서비스 로케이터도 추가됩니다.
  2. 종속성이 어떤 수명을 유지해야하며 어떻게 / 어떻게 정리해야하는지 어떻게 알 수 있습니까?

컨테이너를 사용하여 생성자 주입하면 무료로 얻을 수 있습니다.

ServiceLocator 설정을 잊어 버린 경우 IoC 컨테이너에 새 매핑을 추가하는 것을 잊어 버릴 수 있으며 DI 접근 방식에 동일한 런타임 문제가 발생합니다.

사실입니다. 그러나 생성자 삽입을 사용하면 누락 된 종속성을 파악하기 위해 전체 클래스를 스캔 할 필요가 없습니다.

또한 더 나은 컨테이너는 시작할 때 모든 생성자를 검사하여 모든 종속성을 확인합니다. 따라서 해당 컨테이너를 사용하면 나중에 일시적인 시점이 아닌 런타임 오류가 직접 발생합니다.

또한 저자는 단위 테스트 어려움에 대해 언급했습니다. 그러나 DI 접근법에 문제가 없을까요?

아니요. 정적 서비스 로케이터에 대한 종속성이 없기 때문입니다. 정적 종속성으로 작업하는 병렬 테스트를 받으려고 했습니까? 재미 없어


답변

또한 레거시 코드를 리팩터링하는 경우 Service Locator 패턴이 안티 패턴 일뿐만 아니라 실제적인 필요성이기도합니다. 아무도 수백만 줄의 코드에 마법의 지팡이를 휘두르지 않고 갑자기 모든 코드가 DI 준비가 될 것입니다. 따라서 기존 코드베이스에 DI를 도입하려면 DI 서비스가되도록 변경하는 경우가 종종 있으며 이러한 서비스를 참조하는 코드는 종종 DI 서비스가 아닙니다. 따라서 THOSE 서비스는 DI를 사용하도록 변환 된 서비스의 인스턴스를 가져 오려면 서비스 로케이터를 사용해야합니다.

따라서 DI 개념을 사용하기 위해 대형 레거시 응용 프로그램을 리팩토링 할 때 Service Locator는 안티 패턴 일뿐만 아니라 DI 개념을 코드베이스에 점차적으로 적용 할 수있는 유일한 방법이라고 할 수 있습니다.


답변

테스트 관점에서 Service Locator는 나쁩니다. 8:45 분에 시작되는 코드 예제 http://youtu.be/RlfLCWKxHJ0에 대한 Misko Hevery의 Google Tech Talk에 대한 멋진 설명을 참조하십시오 . 나는 그의 비유가 마음에 들었습니다. 25 달러가 필요하다면 돈을 어디서 가져갈 지 지갑을주는 대신 직접 돈을 요구하십시오. 또한 Service Locator와 필요한 바늘이있는 건초 더미를 비교하고 검색 방법을 알고 있습니다. Service Locator를 사용하는 클래스는 재사용하기가 어렵습니다.


답변

유지 보수 문제 (나를 퍼즐)

이와 관련하여 서비스 로케이터 사용이 나쁜 두 가지 이유가 있습니다.

  1. 귀하의 예에서는 서비스 로케이터에 대한 정적 참조를 클래스에 하드 코딩하고 있습니다. 이 긴밀하게 커플 회전 수단에 서비스 로케이터에 직접 클래스 는 서비스 로케이터없이 작동하지 않습니다 . 또한 단위 테스트 (및 클래스를 사용하는 다른 사람)도 서비스 로케이터에 암시 적으로 의존합니다. 여기서 주목할만한 것은 생성자 주입을 사용할 때 유닛 테스트시 DI 컨테이너가 필요하지 않기 때문에 유닛 테스트 (및 개발자가 이해할 수있는 능력)를 단순화합니다. 이는 생성자 주입을 사용함으로써 얻을 수있는 실현 된 단위 테스트 이점입니다.
  2. 생성자 Intellisense가 중요한 이유는 여기 사람들이 그 요점을 완전히 놓친 것 같습니다. 클래스는 한 번 작성되지만 여러 응용 프로그램 (즉, 여러 DI 구성)에서 사용될 수 있습니다 . 시간이 지남에 따라 (최신의) 문서를 보거나 원래 소스 코드로 돌아 가지 않고 클래스의 종속성을 이해하기 위해 생성자 정의를 볼 수 있다면 배당금을 지불합니다. 클래스의 종속성이 무엇인지 확인하십시오. 서비스 로케이터가있는 클래스는 일반적으로 작성 하기가 쉽지만 프로젝트의 지속적인 유지 관리에서 이러한 편의 비용을 지불하는 것 이상입니다.

단순하고 단순함 : 서비스 로케이터가있는 클래스 는 생성자를 통해 종속성을 허용 하는 클래스 보다 재사용하기더 어렵습니다 .

당신은에서 서비스를 사용해야 할 경우 고려 LibraryA저자가 사용하는 것이 결정했다고 ServiceLocatorA과에서 서비스 LibraryB저자가 사용하는 것이 결정을 ServiceLocatorB. 우리는 프로젝트에서 2 개의 서로 다른 서비스 로케이터를 사용하는 것 외에는 선택의 여지가 없습니다. 문서화, 소스 코드 또는 단축 다이얼 작성자가없는 경우 몇 가지 종속성을 구성해야하는지 추측 할 수 있습니다. 이러한 옵션을 실패하면, 우리는 디 컴파일러를 사용해야 할 수 있습니다 단지종속성이 무엇인지 파악합니다. 완전히 다른 2 개의 서비스 로케이터 API를 구성해야 할 수도 있으며, 디자인에 따라 기존 DI 컨테이너를 단순히 래핑하지 못할 수도 있습니다. 두 라이브러리 사이에 하나의 종속성 인스턴스를 공유하는 것이 불가능할 수도 있습니다. 서비스 로케이터가 실제로 필요한 서비스와 동일한 라이브러리에 상주하지 않으면 프로젝트의 복잡성이 더욱 복잡해질 수 있습니다. 프로젝트에 추가 라이브러리 참조를 내재적으로 끌어 들이고 있습니다.

이제 생성자 주입으로 만든 동일한 두 서비스를 고려하십시오. 에 참조를 추가하십시오 LibraryA. 에 참조를 추가하십시오 LibraryB. DI 구성에서 종속성을 제공하십시오 (Intellisense를 통해 필요한 것을 분석하여). 끝난.

Mark Seemann 은이 이점을 그래픽 형식으로 명확하게 보여주는 StackOverflow 답변을 제공합니다. . 이는 다른 라이브러리에서 서비스 로케이터를 사용할 때뿐만 아니라 서비스에서 외래 기본값을 사용할 때에도 적용됩니다.


답변

내 지식은 이것을 판단하기에 충분하지 않지만 일반적으로 특정 상황에서 무언가가 사용된다면 그것이 반 패턴이 될 수 없다는 것을 의미하지는 않습니다. 특히 타사 라이브러리를 다룰 때 모든 측면을 완벽하게 제어 할 수 없으며 최상의 솔루션을 사용하지 못할 수도 있습니다.

다음은 C #을 통한 Adaptive Code 의 단락입니다 .

“불행히도 서비스 로케이터는 피할 수없는 안티 패턴 일 수 있습니다. 일부 응용 프로그램 유형 (특히 Windows Workflow Foundation)에서 인프라는 생성자 주입에 적합하지 않습니다. 이러한 경우 유일한 대안은 서비스 로케이터를 사용하는 것입니다. 의존성을 전혀 주입하지 않는 것보다 낫다 (anti-) 패턴에 대한 모든 vitriol의 경우 수동으로 의존성을 구성하는 것보다 훨씬 낫다. 결국 데코레이터, 어댑터, 비슷한 이점이 있습니다. “

-홀, 게리 맥린. C #을 통한 적응 코드 : 설계 패턴 및 SOLID 원칙을 사용한 민첩한 코딩 (개발자 참조) (p. 309). 피어슨 교육.


답변

필자는 “컴파일러가 당신을 도울 수 없다”고 주장하고 있으며 이는 사실이다. 클래스를 정복 할 때, 다른 목표 중에서도 인터페이스를 신중하게 선택하여 이해하는 것처럼 독립적으로 만들려고합니다.

클라이언트가 명시 적 인터페이스를 통해 서비스에 대한 참조 (종속성에 대한 참조)를 수락하게하면

  • 암시 적으로 검사를 받으므로 컴파일러가 “도움을줍니다”.
  • 또한 클라이언트가 “Locator”또는 이와 유사한 메커니즘에 대해 알 필요가 없으므로 클라이언트가 실제로 더 독립적입니다.

DI에 문제 / 단점이 있지만 언급 된 장점이 IMO보다 훨씬 큽니다. DI가 인터페이스 (생성자)에 도입 된 종속성이 있다는 것이 옳습니다. 그러나 이것은 당신이 필요로하고 가시적이고 확인 가능하게하려는 매우 의존적 일 것입니다.


답변

예, 서비스 로케이터는 캡슐화솔리드를 위반 하는 안티 패턴 입니다.