ServiceLocator는 안티 패턴입니까?


137

최근 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 컨테이너를 서비스 로케이터로 사용하는 경우 불가능합니다

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


"해당 클래스를 인스턴스화 한 모든 테스트를 업데이트하지 않아도됩니까?" 테스트에서 빌더를 사용하는 경우 반드시 해당되는 것은 아닙니다. 이 경우 빌더 만 업데이트하면됩니다.
피터 Karlsson

당신 말이 맞아요. 예를 들어, 대형 Android 앱에서는 지금까지 저사양 모바일 장치의 성능 문제로 인해 DI를 사용하는 것을 꺼려했습니다. 이러한 경우 테스트 가능한 코드를 작성할 수있는 대안을 찾아야합니다.이 경우 Service Locator가 충분한 대안입니다. (참고 : 새로운 Dagger 2.0 DI 프레임 워크가 충분히 성숙되면 Android에 대한 상황이 변경 될 수 있습니다.)
G. Lombard

1
이 질문이 게시 된 이후, Mark Seemann의 Service Locator 에 대한 업데이트는 Service locator가 캡슐화를 해제하여 OOP를 위반하는 방법을 설명하는 안티 패턴 게시물 입니다. 그는 이전의 모든 주장에 사용했습니다). 2015-10-26 업데이트 : Service Locator의 근본적인 문제는 캡슐화를 위반 한다는 것 입니다.
NightOwl888

답변:


125

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

대신 패턴의 올바른 사용법이 있는지 확인해야하며 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 접근법에 문제가 없을까요?

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


2
Jgauffin, 답변 주셔서 감사합니다. 시작시 자동 확인에 대한 중요한 점을 지적했습니다. 나는 그것에 대해 생각하지 않았고 지금은 DI의 또 다른 이점을 봅니다. "var myType = new MyType ()"예제도 제공했습니다. 그러나 실제 앱에서 종속성을 인스턴스화하지 않기 때문에 유효한 것으로 간주 할 수 없습니다 (IoC 컨테이너는 항상 우리를 위해 수행합니다). 즉 : MVC 앱에는 IMyService 및 MyServiceImpl에 의존하는 컨트롤러가 IMyRepository에 의존합니다. MyRepository 및 MyService를 인스턴스화하지 않습니다. Ctor 매개 변수 (ServiceLocator와 같은)에서 인스턴스를 가져 와서 사용합니다. 우리는 그렇지 않습니까?
davidoff

33
반 패턴이 아닌 Service Locator에 대한 유일한 주장은 "컨트롤 컨테이너의 반전은 서비스 위치가 없으면 작동하지 않는다"입니다. 서비스 로케이터 의도에 대해이 아닌 기계에 대해 때문에 명확하게 설명 된 바와 같이이 인수는하지만, 잘못된 여기에 마크 시만으로 "컴포지션 루트에 캡슐화 된 DI 컨테이너는 서비스 로케이터 아니다 - 그것은 인프라 구성 요소입니다."
Steven

4
@jgauffin Web API는 DI를 컨트롤러에 서비스 위치로 사용하지 않습니다. DI를 전혀하지 않습니다. 수행하는 작업 : 구성 서비스에 전달할 고유 한 ControllerActivator를 작성할 수있는 옵션을 제공합니다. 여기에서 Pure DI이든 컨테이너이든 컴포지션 루트를 만들 수 있습니다. 또한 "서비스 로케이터 패턴"의 정의와 패턴의 구성 요소 인 서비스 위치 사용을 혼합하고 있습니다. 이 정의에서 컴포지션 루트 DI는 "서비스 로케이터 패턴"으로 간주 될 수 있습니다. 그래서 그 정의의 요점은 바보입니다.
Suamere

1
나는 이것이 일반적으로 좋은 대답임을 지적하고 싶습니다. 당신이 한 오해의 소지가있는 점에 대해서만 언급했습니다.
Suamere

2
@jgauffin DI 및 SL은 모두 IoC의 버전 변환입니다. SL은 잘못된 길입니다. IoC 컨테이너 SL이거나 DI를 사용할 수 있습니다. 배선 방법에 따라 다릅니다. 그러나 SL은 나쁘고 나쁘다. 모든 것을 밀접하게 결합하고 있다는 사실을 숨기는 방법입니다.
MirroredFate

37

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

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


12
레거시 코드를 처리 할 때는 중간 단계 (불완전한 단계)를 수행하더라도 모든 문제를 해결해야합니다. 서비스 로케이터는 이러한 중간 단계입니다. 문서화되고 반복 가능하며 효과적인 것으로 입증 된 대체 솔루션이 존재한다는 사실을 기억하는 한, 한 번에 한 단계 씩 지옥에서 벗어날 수 있습니다 . 이 대체 솔루션은 Dependency Injection이며, 이는 Service Locator가 여전히 안티 패턴 인 이유입니다. 올바르게 설계된 소프트웨어는 이것을 사용하지 않습니다.
Steven

RE : "레거시 코드를 다룰 때 모든 것이 그 혼란에서 벗어나도록 정당화됩니다."때로는 존재했던 레거시 코드가 조금만 있는지 궁금하지만 어떻게 든 그렇게 할 수 없었습니다.
Drew Delano

8

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


10
이것은 재활용 된 의견이며 의견으로 더 나았을 것입니다. 또한, 귀하의 (그의?) 비유는 일부 패턴이 다른 패턴보다 일부 문제에 더 적합하다는 것을 증명하는 역할을한다고 생각합니다.
8bitjunkie

6

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

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

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

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

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

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

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


1

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

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

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

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


0

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

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

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

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


Zrin, 당신의 생각에 감사합니다. 알다시피, "적절한"DI 접근법을 사용하면 단위 테스트를 제외한 모든 곳에서 종속성을 인스턴스화해서는 안됩니다. 따라서 컴파일러는 테스트를 통해서만 나를 도울 것입니다. 그러나 원래의 질문에서 설명했듯이 테스트가 실패한이 "도움말"은 아무 것도 제공하지 않습니다. 그렇습니까?
davidoff

'정적 정보'/ '컴파일 타임 확인'인수는 짚맨입니다. @davidoff가 지적했듯이 DI는 런타임 오류에 똑같이 취약합니다. 또한 최신 IDE는 멤버에게 주석 / 요약 정보에 대한 툴팁보기를 제공하며, 그렇지 않은 경우에도 누군가 API를 '알고있을 때까지 문서를 계속 볼 것입니다. 설명서는 필수 생성자 매개 변수인지 또는 종속성 구성 방법에 대한 설명인지에 대한 설명서입니다.
tuespetre

구현 / 코딩 및 품질 보증에 대한 생각-코드의 가독성은 특히 인터페이스 정의에 중요합니다. 자동 컴파일 시간 검사없이 할 수 있고 인터페이스를 적절하게 주석 처리 / 문서화하면 쉽게 볼 수 없거나 예측할 수없는 내용으로 전역 변수의 종류에 대한 숨겨진 의존성의 단점을 부분적으로 해결할 수 있다고 생각합니다. 이 단점을 능가하는이 패턴을 사용해야 할 충분한 이유가 필요하다고 말하고 싶습니다.
Zrin

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.