답변:
싱글턴에 대한 두 가지 주요 비판은 내가 관찰 한 것에서 두 개의 수용소에 속합니다.
이 두 가지의 결과로 일반적인 접근 방식은 광범위한 컨테이너 객체를 생성하여 이러한 클래스의 단일 인스턴스를 보유하고 컨테이너 객체 만 이러한 유형의 클래스를 수정하는 반면 다른 많은 클래스는 액세스 할 수있는 액세스 권한을 부여하는 것입니다 컨테이너 객체
나는 그것이 반 패턴이라는 것에 동의합니다. 왜? 코드가 의존성에 대해 거짓말을 할 수 있기 때문에 다른 프로그래머가 이전에 변경할 수없는 싱글 톤에서 변경 가능한 상태를 도입하지 않도록 신뢰할 수 없기 때문입니다.
클래스에는 문자열 만 가져 오는 생성자가있을 수 있으므로 클래스가 격리되어 인스턴스화되고 부작용이 없다고 생각합니다. 그러나 조용히, 어떤 종류의 공개적으로 사용 가능한 단일 객체와 통신하므로 클래스를 인스턴스화 할 때마다 다른 데이터가 포함됩니다. 이는 API 사용자뿐만 아니라 코드의 테스트 가능성에도 큰 문제입니다. 코드를 올바르게 단위 테스트하려면 일관된 테스트 결과를 얻으려면 싱글 톤에서 전역 상태를 미세 관리하고 인식해야합니다.
싱글 톤 패턴은 기본적으로 느리게 초기화 된 전역 변수입니다. 전역 변수는 일반적으로 옳고 그름은 악의적 인 것으로 간주됩니다. 그러나 IMHO는 프로그램의 초기화 루틴의 일부로 (예 : 구성 파일 또는 명령 행 인수를 읽음으로써) 한 번에 한 번 설정되고 이후에 상수로 취급되는 전역 변수에는 아무런 문제가 없습니다. 전역 변수의 이러한 사용은 컴파일 타임에 선언 된 명명 된 상수를 갖는 것과는 정신적으로가 아니라 문자로만 다릅니다.
마찬가지로 Singletons에 대한 나의 의견은 프로그램의 관련이없는 것으로 보이는 부분 사이에서 변경 가능한 상태를 전달하는 데 사용되는 경우에만 나쁘다는 것입니다. 변경 가능한 상태를 포함하지 않거나 포함하는 변경 가능한 상태가 완전히 캡슐화되어 객체의 사용자가 멀티 스레드 환경에서도 객체에 대해 알 필요가없는 경우에는 아무런 문제가 없습니다.
PHP 세계에서 꽤 많은 싱글 톤을 보았습니다. 패턴이 정당화 된 것을 발견 한 유스 케이스를 기억하지 못합니다. 그러나 사람들이 왜 그것을 사용했는지에 대한 아이디어가 있다고 생각합니다.
단일 인스턴스 .
"응용 프로그램 전체에서 클래스 C의 단일 인스턴스를 사용하십시오."
이는 "기본 데이터베이스 연결"과 같은 합리적인 요구 사항입니다. 두 번째 db 연결을 만들지 않는다는 의미는 아니며 일반적으로 기본 연결을 사용한다는 의미입니다.
단일 인스턴스화 .
"클래스 C가 두 번 이상 (프로세스 당, 요청 당 등) 인스턴스화되지 않도록하십시오."
이것은 클래스를 인스턴스화 할 때 다른 인스턴스와 충돌하는 부작용이있는 경우에만 해당됩니다.
구성 요소를 다시 디자인하면 (예 : 클래스 생성자에서 부작용을 제거하여) 이러한 충돌을 피할 수 있습니다. 또는 다른 방법으로 해결할 수도 있습니다. 그러나 여전히 일부 정당한 사용 사례가있을 수 있습니다.
또한 "하나만"요구 사항이 "프로세스 당 하나"를 의미하는지 여부도 고려해야합니다. 예를 들어 리소스 동시성에 대한 요구 사항은 "프로세스 당 하나"가 아니라 "프로세스 전반에 걸쳐 하나의 전체 시스템 당 하나"입니다. 그리고 다른 것들에 대해서는 "응용 프로그램 컨텍스트"에 따른 것이며 프로세스 당 하나의 응용 프로그램 컨텍스트를 갖습니다.
그렇지 않으면이 가정을 시행 할 필요가 없습니다. 이를 적용하면 단위 테스트를 위해 별도의 인스턴스를 만들 수 없습니다.
글로벌 액세스.
개체가 사용되는 곳으로 개체를 전달할 적절한 인프라가없는 경우에만 합법적입니다. 이것은 당신의 프레임 워크 나 환경이 엉망이 될 수 있지만, 그것을 고치는 힘 안에 있지 않을 수도 있습니다.
가격은 긴밀한 결합, 숨겨진 의존성 및 글로벌 상태에 대해 나쁜 모든 것입니다. 그러나 당신은 아마 이미 이런 고통을 겪고 있습니다.
게으른 인스턴스화.
이것은 싱글 톤의 필수 부분은 아니지만 구현하는 가장 일반적인 방법 인 것 같습니다. 그러나 게으른 인스턴스화는 좋은 일이지만 실제로 달성하기 위해서는 싱글 톤이 필요하지 않습니다.
일반적인 구현은 전용 생성자와 정적 인스턴스 변수 및 지연 인스턴스화가있는 정적 getInstance () 메소드가있는 클래스입니다.
위에서 언급 한 문제 외에도, 클래스가 이미 가지고있는 다른 책임 외에도 클래스가 자체 인스턴스화 및 수명주기를 제어 하기 때문에 단일 책임 원칙 을 적용합니다.
대부분의 경우 단일 상태 및 전역 상태없이 동일한 결과를 얻을 수 있습니다. 대신 의존성 주입을 사용해야하며 의존성 주입 컨테이너 를 고려할 수 있습니다 .
그러나 다음과 같은 유효한 요구 사항이 남아있는 사용 사례가 있습니다.
따라서이 경우 수행 할 수있는 작업은 다음과 같습니다.
공용 생성자를 사용하여 인스턴스화하려는 클래스 C를 만듭니다.
정적 인스턴스 변수가있는 별도의 클래스 S와 지연 인스턴스화가있는 정적 S :: getInstance () 메소드를 작성하면 인스턴스에 클래스 C가 사용됩니다.
C 생성자의 모든 부작용을 제거하십시오. 대신 이러한 부작용을 S :: getInstance () 메소드에 넣으십시오.
위 요구 사항을 가진 클래스가 두 개 이상인 경우 소규모 로컬 서비스 컨테이너로 클래스 인스턴스를 관리하고 컨테이너 에 대해서만 정적 인스턴스를 사용하는 것이 좋습니다. 따라서 S :: getContainer ()는 지연된 서비스 컨테이너를 제공하고 컨테이너에서 다른 객체를 가져옵니다.
정적 getInstance ()를 호출하지 마십시오. 가능할 때마다 종속성 주입을 사용하십시오. 특히 서로 의존하는 여러 객체와 함께 컨테이너 접근 방식을 사용하는 경우 S :: getContainer ()를 호출 할 필요가 없습니다.
선택적으로 클래스 C가 구현하는 인터페이스를 작성하고이를 사용하여 S :: getInstance ()의 리턴 값을 문서화하십시오.
(우리는 여전히 이것을 싱글 톤이라고 부릅니까? 나는 이것을 코멘트 섹션에 남겨 둡니다.)
혜택:
전역 상태를 건드리지 않고 단위 테스트를 위해 별도의 C 인스턴스를 만들 수 있습니다.
인스턴스 관리는 클래스 자체와 분리되어 있습니다.-> 우려 분리, 단일 책임 원칙.
S :: getInstance ()가 인스턴스에 대해 다른 클래스를 사용하도록하거나 심지어 사용할 클래스를 동적으로 결정하는 것은 매우 쉽습니다.
개인적으로 1, 2 또는 3 또는 특정 클래스에 대해 제한된 양의 객체가 필요할 때 싱글 톤을 사용합니다. 또는 클래스의 여러 인스턴스가 올바르게 작동하지 않도록 클래스의 사용자에게 전달하고 싶습니다.
또한 코드의 거의 모든 곳에서 사용해야 할 때만 사용하고 객체를 매개 변수로 필요한 각 클래스 또는 함수에 전달하고 싶지 않습니다.
또한 다른 함수의 참조 투명성을 손상시키지 않으면 단일 톤만 사용합니다. 일부 입력이 주어지면 항상 동일한 출력을 생성합니다. 즉, 나는 그것을 세계 국가에 사용하지 않습니다. 가능하지 않으면 전역 상태가 한 번 초기화되고 절대 변경되지 않습니다.
사용하지 않을 때는 위의 3을 참조하여 반대쪽으로 변경하십시오.