싱글 톤이 나쁘면 서비스 컨테이너가 좋은 이유는 무엇입니까?


91

우리는 Singleton 이 얼마나 나쁜지 알고 있습니다. 그 이유는 의존성과 다른 이유로 숨기기 때문 입니다.

그러나 프레임 워크에는 한 번만 인스턴스화 하고 모든 곳에서 호출해야하는 많은 객체 (로거, DB 등)가있을 수 있습니다.

이 문제를 해결하기 위해 서비스 (로거 등)에 대한 모든 참조를 내부적으로 저장하는 소위 "개체 관리자"(또는 심포니와 같은 서비스 컨테이너 ) 를 사용하라는 지시를 받았습니다.

하지만 서비스 제공 업체가 순수한 싱글 톤만큼 나쁘지 않은 이유는 무엇입니까?

서비스 공급자는 종속성도 숨기고 첫 번째 인스턴스 생성을 래핑합니다. 그래서 싱글 톤 대신 서비스 제공 업체를 사용해야하는 이유를 이해하기 위해 정말 고심하고 있습니다.

추신. 종속성을 숨기지 않으려면 DI를 사용해야한다는 것을 알고 있습니다 (Misko가 말한대로)

더하다

나는 추가 할 것이다 : 요즘 싱글 톤은 그렇게 사악하지 않다. PHPUnit의 제작자는 여기에서 설명했다.

DI + Singleton은 문제를 해결합니다.

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

이것이 모든 문제를 전혀 해결하지 못하더라도 꽤 똑똑합니다.

DI 및 서비스 컨테이너 외에이 도우미 개체에 액세스 할 수있는 좋은 솔루션이 있습니까?


2
@yes 귀하의 편집은 잘못된 가정을하고 있습니다. Sebastian은 코드 스 니펫이 싱글 온 사용을 덜 문제로 만든다고 결코 제안하지 않습니다. 다른 방법으로는 더 테스트하기가 불가능한 코드를 만드는 한 가지 방법 일뿐입니다. 그러나 여전히 문제가있는 코드입니다. 사실 그는 "당신이 할 수 있다고해서 그렇게해야한다는 것을 의미하지는 않는다"고 명시 적으로 언급합니다. 올바른 해결책은 여전히 ​​Singleton을 전혀 사용하지 않는 것입니다.
Gordon

3
@yes는 SOLID 원칙을 따릅니다.
Gordon

19
나는 싱글 톤이 나쁘다는 주장에 이의를 제기합니다. 오용 될 수 있지만 어떤 도구도 마찬가지 입니다. 메스를 사용하여 생명을 구하거나 끝낼 수 있습니다. 전기 톱은 산불을 방지하기 위해 숲을 뚫거나, 당신이 무엇을하고 있는지 모르는 경우 팔의 상당한 부분을 잘라낼 수 있습니다. 도구를 현명하게 사용하는 법을 배우고 조언을 복음으로 취급 하지 마십시오 . 그런 식으로 생각 하지 않는 마음이 있습니다.
paxdiablo

4
@paxdiablo하지만 그들은 이다 나쁜. 싱글 톤은 SRP, OCP 및 DIP를 위반합니다. 그들은 글로벌 상태와 긴밀한 결합을 애플리케이션에 도입하고 API가 종속성에 대해 거짓말을하게 만듭니다. 이 모든 것은 코드의 유지 관리 성, 가독성 및 테스트 가능성에 부정적인 영향을 미칩니다. 이러한 단점이 작은 이점보다 중요한 경우가 드물지만 99 %에서는 싱글 톤이 필요하지 않다고 주장합니다. 특히 Singleton이 요청에 대해서만 고유하고 빌더에서 협력자 그래프를 조립하는 것이 더럽습니다.
Gordon

5
아니, 나는 그렇게 믿지 않는다. 도구는 함수를 수행하는 수단입니다. 일부 (emacs?)는 더 어렵게 만드는 드문 구별이 있지만 일반적으로 어떻게 든 쉽게 만들 수 있습니다. :-)이 점에서 싱글 톤은 균형 잡힌 트리 나 컴파일러와 다르지 않습니다. . 객체의 복사본을 하나만 확인해야하는 경우 싱글 톤이이를 수행합니다. 그것이 되는지 여부는 논란의 여지가 있지만, 그것이 전혀하지 않는다고 주장 할 수 있다고 믿지 않습니다. 전기 톱이 톱보다 빠르거나 네일 건 대 망치와 같은 더 좋은 방법이있을 수 있습니다. 그렇다고 톱 / 해머가 도구가되지는 않습니다.
paxdiablo 2011 년

답변:


76

서비스 로케이터는 말할 것도없이 두 가지 악 중 더 적은 것입니다. 이 네 가지 차이점으로 요약되는 "작은"( 적어도 지금은 다른 것을 생각할 수 없습니다 ) :

단일 책임 원칙

Service Container는 Singleton처럼 단일 책임 원칙을 위반하지 않습니다. 싱글 톤은 개체 생성과 비즈니스 로직을 혼합하는 반면 서비스 컨테이너는 애플리케이션의 개체 수명주기를 관리하는 데 전적으로 책임이 있습니다. 그런 점에서 서비스 컨테이너가 더 좋습니다.

커플 링

싱글 톤은 일반적으로 정적 메서드 호출로 인해 애플리케이션에 하드 코딩되므로 코드에서 밀접하게 결합되고 모의 종속성 이 발생하기 어렵습니다 . 반면에 SL은 하나의 클래스이며 주입 될 수 있습니다. 따라서 모든 클래스가 그것에 의존하지만 적어도 느슨하게 결합 된 종속성입니다. 따라서 ServiceLocator를 Singleton 자체로 구현하지 않는 한 다소 더 좋고 테스트하기도 쉽습니다.

그러나 ServiceLocator를 사용하는 모든 클래스는 이제 커플 링의 한 형태 인 ServiceLocator에 의존하게됩니다. 이것은 ServiceLocator에 대한 인터페이스를 사용하여 완화 할 수 있으므로 구체적인 ServiceLocator 구현에 묶여 있지는 않지만 클래스는 일종의 Locator의 존재에 의존하지만 ServiceLocator를 전혀 사용하지 않으면 재사용이 크게 증가합니다.

숨겨진 종속성

종속성을 숨기는 문제는 매우 많이 존재합니다. 로케이터를 소비하는 클래스에 주입하면 종속성을 알 수 없습니다. 그러나 Singleton과 달리 SL은 일반적으로 백그라운드에서 필요한 모든 종속성을 인스턴스화합니다. 따라서 서비스를 가져올 때 CreditCard 예제에서 Misko Hevery 처럼 끝나지 않습니다. 를 들어 종속성의 모든 종속성을 수동으로 인스턴스화 할 필요가 없습니다.

인스턴스 내부에서 종속성을 가져 오는 것도 공동 작업자를 파헤쳐서는 안된다는 Demeter의 법칙을 위반 하는 것입니다. 인스턴스는 직속 공동 작업자와 만 대화해야합니다. 이것은 Singleton과 ServiceLocator 모두의 문제입니다.

글로벌 상태

글로벌 상태의 문제는 테스트 사이에 새 서비스 로케이터를 인스턴스화 할 때 이전에 생성 된 모든 인스턴스도 삭제되기 때문에 다소 완화됩니다 (실수를 만들어 SL의 정적 속성에 저장하지 않는 한). 물론 SL이 관리하는 클래스의 전역 상태에는 적용되지 않습니다.

또한 훨씬 더 심층적 인 논의를 위해 Fowler on Service Locator vs Dependency Injection 을 참조하십시오 .


Singletons를 사용하는 코드 테스트에 대한 Sebastian Bergmann 의 업데이트 및 링크 된 기사에 대한 참고 사항 : Sebastian은 제안 된 해결 방법이 Singleons를 사용하는 데 문제가 덜하다는 것을 결코 제안하지 않습니다. 다른 방법으로는 테스트 할 수없는 코드를 테스트 할 수있는 방법 중 하나 일뿐입니다. 그러나 여전히 문제가있는 코드입니다. 사실 그는 "당신이 할 수 있다고해서 그렇게해야한다는 것을 의미하지는 않는다"고 명시 적으로 언급합니다.


1
특히 여기에서 테스트 가능성을 적용해야합니다. 정적 메서드 호출을 모의 할 수 없습니다. 그러나 생성자 또는 설정자를 통해 주입 된 서비스를 모의 처리 할 수 ​​있습니다.
David

44

서비스 로케이터 패턴은 안티 패턴입니다. 종속성 노출 문제를 해결하지 못합니다 (클래스 정의를 보면 종속성이 주입되지 않고 서비스 로케이터에서 빠져 나가기 때문에 어떤 종속성인지 알 수 없습니다).

따라서 귀하의 질문은 서비스 로케이터가 좋은 이유입니다. 내 대답은 그렇지 않습니다.

피하고, 피하고, 피하십시오.


6
인터페이스에 대해 아무것도 모르는 것 같습니다. 클래스는 생성자 시그니처에 필요한 인터페이스를 설명 할 뿐이며 그가 알아야 할 모든 것입니다. Passed Service Locator는 인터페이스를 구현해야합니다. IDE가 인터페이스 구현을 확인하면 변경 사항을 제어하기가 매우 쉽습니다.
OZ_

4
@ yes123 : SL이 반 패턴이기 때문에 틀렸다고 말하는 사람들. 귀하의 질문은 "SL이 좋은 이유는 무엇입니까?"입니다. 내 대답은 그렇지 않습니다.
jason

5
SL이 anit-pattern인지 아닌지는 논쟁하지 않겠지 만, 싱글 톤과 글로벌과 비교할 때 훨씬 더 적은 악이라고 말할 것입니다. 싱글 톤에 의존하는 클래스는 테스트 할 수 없지만 SL에 의존하는 클래스는 확실히 테스트 할 수 있습니다 (SL 설계를 작동하지 않는 지점까지 망칠 수는 있지만). 주목 ...
ircmaxell

3
@Jason은 Interface를 구현하는 객체를 전달해야합니다. 클래스 생성자의 정의만으로 자신을 제한하고 있으며 모든 클래스 (인터페이스 아님)를 생성자에 작성하고 싶습니다. 어리석은 생각입니다. 인터페이스 만 있으면됩니다. 모의로이 클래스를 성공적으로 테스트 할 수 있고, 코드를 변경하지 않고 동작을 쉽게 변경할 수 있으며, 추가 종속성 및 커플 링이 없습니다. 이것이 (일반적으로) 종속성 주입에서 원하는 것입니다.
OZ_

2
물론 데이터베이스, 로거, 디스크, 템플릿, 캐시 및 사용자를 하나의 "입력"개체에 함께 넣을 것입니다. 컨테이너를 사용했을 때보 다 개체가 의존하는 종속성을 쉽게 알 수 있습니다.
Mahn

4

서비스 컨테이너는 Singleton 패턴처럼 종속성을 숨 깁니다. 서비스 컨테이너의 모든 장점은 있지만 서비스 컨테이너의 단점은 없기 때문에 종속성 주입 컨테이너를 대신 사용하는 것이 좋습니다.

내가 이해하는 한, 둘 사이의 유일한 차이점은 서비스 컨테이너에서 서비스 컨테이너는 주입되는 개체 (따라서 종속성 숨기기)이며 DIC를 사용할 때 DIC가 적절한 종속성을 주입한다는 것입니다. DIC에 의해 관리되는 클래스는 DIC에 의해 관리된다는 사실을 완전히 인식하지 못하므로 결합이 적고 종속성이 명확하며 단위 테스트가 만족 스럽습니다.

이것은 두 가지의 차이점을 설명하는 좋은 질문입니다 . 종속성 주입과 서비스 로케이터 패턴의 차이점은 무엇입니까?


"DIC는 적절한 종속성을 주입합니다."Singleton에서도 이런 일이 발생하지 않습니까?
동적

5
@ yes123-Singleton을 사용하는 경우 주입하지 않을 것입니다. 대부분의 경우 전역 적으로 액세스합니다 (이것이 Singleton의 요점입니다). Singleton 을 주입 하면 종속성을 숨기지 않지만 Singleton 패턴의 원래 목적에 위배된다고 생각합니다.이 클래스를 전역 적으로 액세스 할 필요가 없는지 스스로에게 물어볼 것입니다. 싱글 톤으로 만들어야합니까?
rickchristie

2


1) 상속 (Object Manager 클래스 상속 및 메서드 재정의 가능) 으로 서비스 컨테이너의 개체를 쉽게 교체 할 수 있기 때문에
2) 구성 변경 (Symfony의 경우)

그리고 Singleton은 높은 결합 때문에뿐만 아니라 _ Single _tons 이기 때문에 나쁩니다. 거의 모든 종류의 객체에 대해 잘못된 아키텍처입니다.

'순수한'DI (생성자에서)를 사용하면 매우 큰 비용을 지불하게됩니다. 모든 객체는 생성자에 전달되기 전에 생성되어야합니다. 더 많은 메모리를 사용하고 성능은 떨어집니다. 또한 항상 객체가 생성자에서 생성되고 전달 될 수있는 것은 아닙니다. 종속성 체인을 생성 할 수 있습니다. 내 영어는 그것에 대해 완전히 논의하기에 충분하지 않습니다. Symfony 문서에서 이에 대해 읽어보십시오.


0

저에게는 간단한 이유로 전역 상수, 싱글 톤을 피하려고합니다. API를 실행해야하는 경우가 있습니다.

예를 들어 프론트 엔드와 관리자가 있습니다. 관리자 내부에서 사용자로 로그인 할 수 있기를 바랍니다. 관리자 내부의 코드를 고려하십시오.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

이것은 프런트 엔드 초기화를위한 새로운 데이터베이스 연결, 새로운 로거 등을 설정하고 사용자가 실제로 존재하는지, 유효한지 등을 확인할 수 있습니다. 또한 적절한 별도의 쿠키 및 위치 서비스를 사용합니다.

싱글 톤에 대한 내 생각은-부모 안에 동일한 객체를 두 번 추가 할 수 없습니다. 예를 들어

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

하나의 인스턴스와 두 변수 모두를 가리킬 것입니다.

마지막으로 객체 지향 개발을 사용하려면 클래스가 아닌 객체로 작업하십시오.


1
그래서 당신의 방법은 $api 프레임 워크 주위 에 var 를 전달하는 것 입니까? 무슨 말인지 정확히 이해하지 못했습니다. 또한 호출 add('Logger')이 동일한 인스턴스를 반환하는 경우 기본적으로 서비스 코 테이너가 있습니다
dynamic

그래 맞아. 나는 그것들을 "시스템 컨트롤러"라고 부르며 API의 기능을 향상시키기위한 것입니다. 비슷한 방식으로 "감사 가능"컨트롤러를 모델에 두 번 추가하면 동일한 방식으로 작동합니다. 하나의 인스턴스와 하나의 감사 필드 집합 만 만듭니다.
romaninsh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.