싱글 톤이 나쁘다면 어떻게해야할까요?


553

최근 싱글 톤 사용 (과용) 문제에 대해 많은 논의가있었습니다. 나는 내 경력 초기에 그 사람들 중 하나였습니다. 나는 지금 문제가 무엇인지 알 수 있지만, 여전히 좋은 대안을 찾을 수없는 경우가 많지만, 많은 안티 싱글 톤 토론이 실제로 하나를 제공하지는 않습니다.

다음은 제가 최근에 참여한 주요 프로젝트의 실제 예입니다.

응용 프로그램은 너무 자주 업데이트되지 않는 서버 상태에서 많은 양의 데이터를 사용하는 별도의 화면과 구성 요소가 많은 씩 클라이언트였습니다. 이 데이터는 기본적으로 단일 "관리자"개체 (두려운 "전역 상태")에 캐시되었습니다. 아이디어는 데이터를 저장하고 동기화하는 앱에이 장소를 두는 것이 었으며, 열린 새 화면은 서버에서 다양한 지원 데이터를 반복적으로 요청하지 않고도 필요한 부분을 대부분 쿼리 할 수 ​​있습니다. 끊임없이 서버에 요청하면 너무 많은 대역폭이 필요합니다. 일주일에 수천 달러의 추가 인터넷 요금을 청구하고 있기 때문에 받아 들일 수 없었습니다.

기본적으로 이런 종류의 전역 데이터 관리자 캐시 개체를 갖는 것보다 여기에 적절한 다른 접근 방법이 있습니까? 이 객체는 공식적으로 "싱글 톤"일 필요는 없지만 개념적으로는 하나의 의미가 있습니다. 좋은 깨끗한 대안은 무엇입니까?


10
Singleton을 사용하면 어떤 문제가 해결됩니까? 정적 클래스와 같은 대안보다 문제를 해결하는 것이 어떻게 더 낫습니까?
아논.

14
@Anon : 정적 클래스를 사용하면 상황이 어떻게 향상됩니까? 여전히 타이트한 커플 링이 있습니까?
Martin York

5
@ 마틴 : 나는 그것이 "더 나은"것을 제안하지 않습니다. 나는 대부분의 경우 싱글 톤이 문제를 찾는 해결책이라고 제안합니다.
아논.

9
@Anon : 사실이 아닙니다. 정적 클래스는 인스턴스화를 거의 제어하지 않고 싱글 톤보다 멀티 스레딩을 훨씬 어렵게 만듭니다 (인스턴스 대신 모든 개별 메서드에 대한 액세스를 직렬화해야하기 때문에). 싱글 톤은 또한 정적 클래스가 할 수없는 인터페이스를 구현할 수 있습니다. 정적 클래스는 확실히 장점이 있지만,이 경우 싱글 톤은 확실히 두 가지 악한 것보다 적습니다. 구현하는 정적 클래스 어떤 무엇이든지 변경 가능한 상태 "경고! : 나쁜 디자인을 AHEAD"큰 깜박이는 네온처럼 기호.
Aaronaught

7
@Aaronaught : 싱글 톤에 대한 액세스를 동기화하는 경우 동시성이 손상 됩니다. 싱글 톤 객체를 가져온 직후에 스레드가 중단 될 수 있으며 다른 스레드가 발생하고 경쟁 조건을 비난 할 수 있습니다. 대부분의 경우 정적 클래스 대신 Singleton을 사용 하면 경고 표시를 없애고 문제를 해결할 수 있습니다 .
아논.

답변:


809

여기서 단일 인스턴스Singleton 디자인 패턴 을 구분하는 것이 중요합니다 .

단일 인스턴스 는 단순히 현실입니다. 대부분의 앱은 한 번에 하나의 구성, 한 번에 하나의 UI, 한 번에 하나의 파일 시스템 등 만 작동하도록 설계되었습니다. 유지해야 할 상태 나 데이터가 많으면 확실히 하나의 인스턴스 만 갖고 가능한 한 오래 유지해야합니다.

싱글 톤 디자인 패턴 은 매우 특정한 유형 의 단일 인스턴스, 특히 다음과 같은 유형 입니다.

  • 전역 정적 인스턴스 필드를 통해 액세스 할 수 있습니다.
  • 프로그램 초기화 또는 최초 액세스시 작성됩니다.
  • 공개 생성자가 없습니다 (직접 인스턴스화 할 수 없음).
  • 명시 적으로 해제하지 마십시오 (프로그램 종료시 암시 적으로 해제).

이 특정 설계 선택으로 인해 패턴에 몇 가지 잠재적 인 장기 문제가 발생합니다.

  • 추상 또는 인터페이스 클래스를 사용할 수 없음
  • 서브 클래스 불가능
  • 응용 분야에서 높은 결합 (수정하기 어려움)
  • 테스트하기 어려움 (단위 테스트에서 위조 / 조롱 할 수 없음);
  • 변경 가능한 상태의 경우 병렬화하기 어려움 (광범위한 잠금이 필요함);
  • 등등.

이 증상들 중 어느 것도 실제로 단일 인스턴스에만 해당되는 것이 아니라 싱글 톤 패턴 일뿐입니다.

대신 무엇을 할 수 있습니까? 싱글 톤 패턴을 사용하지 마십시오.

질문에서 인용 :

아이디어는 데이터를 저장하고 동기화하는 앱에이 장소를 두는 것이 었으며, 열린 새 화면은 서버에서 다양한 지원 데이터를 반복적으로 요청하지 않고도 필요한 부분을 대부분 쿼리 할 수 ​​있습니다. 끊임없이 서버에 요청하면 너무 많은 대역폭이 필요합니다. 일주일에 수천 달러의 추가 인터넷 요금을 청구하고 있기 때문에 받아 들일 수 없었습니다.

이 개념에는 힌트가 있지만 확실하지 않은 이름이 있습니다. 이것을 캐시 라고 합니다 . 화려하고 싶다면 "오프라인 캐시"또는 원격 데이터의 오프라인 복사본이라고 부를 수 있습니다.

캐시는 싱글 톤일 필요는 없습니다. 그것은 여러 캐시 인스턴스에 대한 동일한 데이터를 가져 오는 피하려는 경우 하나의 인스턴스가 필요; 그렇다고해서 실제로 모든 사람에게 모든 것을 노출 시켜야한다는 의미는 아닙니다 .

가장 먼저 할 일은 캐시 의 다른 기능 영역 을 별도의 인터페이스로 분리하는 것입니다. 예를 들어 Microsoft Access를 기반으로 세계에서 가장 최악의 YouTube 복제본을 만들고 있다고 가정 해 보겠습니다.

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

여기에는 미디어, 사용자 프로필 및 정적 페이지 (예 : 프론트 페이지)와 같이 특정 클래스가 액세스해야하는 특정 유형의 데이터를 설명하는 몇 가지 인터페이스 가 있습니다. 이 모든 것은 하나의 메가 캐시 로 구현 되지만 대신 인터페이스를 허용하도록 개별 클래스를 설계하므로 인스턴스의 종류에 상관하지 않습니다. 프로그램이 시작될 때 실제 인스턴스를 한 번 초기화 한 다음 생성자와 공용 속성을 통해 인스턴스를 특정 인터페이스 유형으로 캐스트하기 시작하면됩니다.

그건 그렇고, 의존성 주입 이라고 합니다; 일반 클래스 디자인 이 호출자로부터 자신인스턴스를 인스턴스화 하거나 전역 상태를 참조 하는 대신 호출자의 종속성을 허용 하는 한 Spring 또는 특수 IoC 컨테이너를 사용할 필요가 없습니다 .

왜 인터페이스 기반 디자인을 사용해야합니까? 세 가지 이유 :

  1. 코드를보다 쉽게 ​​읽을 수 있습니다. 인터페이스 에서 종속 클래스가 의존 하는 데이터를 정확하게 이해할 수 있습니다 .

  2. Microsoft Access가 데이터 백엔드에 가장 적합하지 않다는 것을 알게되면 SQL Server라고하겠습니다.

  3. SQL Server가 미디어에 특히 적합하지 않다는 것을 알고 있다면 시스템의 다른 부분에 영향을주지 않고 구현 중단 할 수 있습니다 . 그것이 바로 추상 추상화의 힘이 들어오는 곳입니다.

한 걸음 더 나아가려면 Spring (Java) 또는 Unity (.NET)와 같은 IoC 컨테이너 (DI 프레임 워크)를 사용할 수 있습니다. 거의 모든 DI 프레임 워크는 자체 수명 관리를 수행하며 특정 서비스 를 단일 인스턴스로 정의 할 수 있습니다 (종종 "싱글 톤"이라고 부르지 만 이는 친숙 함을위한 것입니다). 기본적으로 이러한 프레임 워크는 인스턴스를 수동으로 전달하는 원숭이 작업의 대부분을 저장하지만 반드시 필요한 것은 아닙니다. 이 디자인을 구현하기 위해 특별한 도구가 필요하지 않습니다.

완벽을 기하기 위해 위의 디자인도 이상적이지 않다는 점을 지적해야합니다. 캐시를 처리 할 때 (있는 그대로) 실제로 완전히 별도의 레이어 가 있어야합니다 . 다시 말해, 이와 같은 디자인 :

                                                        +-IMediaRepository
                                                        |
                          캐시 (일반) --------------- +-IProfileRepository
                                ▲ |
                                | +-IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

이것의 장점은 Cache리팩토링하기로 결정한 경우 인스턴스 를 해체 할 필요가 없다는 것입니다 . 대체 구현을 제공하여 미디어가 저장되는 방식을 간단하게 변경할 수 있습니다 IMediaRepository. 이것이 어떻게 결합되는지에 대해 생각하면 캐시의 물리적 인스턴스를 하나만 작성하므로 동일한 데이터를 두 번 가져올 필요가 없습니다.

세계의 모든 단일 소프트웨어가 높은 응집력과 느슨한 결합의 정확한 표준에 맞게 설계 될 필요는 없습니다. 프로젝트의 규모와 범위, 팀, 예산, 마감일 등에 따라 달라집니다. 그러나 최상의 디자인이 무엇인지 (단일 대신 사용) 요구하는 경우에는 이것이됩니다.

추신 다른 사람들이 언급했듯이 종속 클래스가 캐시를 사용하고 있다는 것을 인식하는 것이 가장 좋은 아이디어는 아닙니다. 즉, 결코 신경 쓰지 않아야하는 구현 세부 사항입니다. 즉, 전체 아키텍처는 여전히 위의 그림과 매우 유사하게 보일 것 입니다. 개별 인터페이스를 Caches 라고 부르지 는 않습니다 . 대신 서비스 또는 이와 유사한 이름을 지정하십시오 .


131
첫 번째 글은 DI를 글로벌 상태의 대안으로 실제로 설명하는 글을 읽었습니다. 시간과 노력에 감사드립니다. 이 게시물의 결과로 우리 모두 더 나아졌습니다.
MrLane

4
왜 캐시가 싱글 톤이 될 수 없습니까? 당신이 그것을 전달하고 의존성 주입을 사용한다면 싱글 톤이 아닙니까? Singleton은 액세스 방법이 아니라 하나의 인스턴스로 제한하는 것입니까? :이 걸릴 내보기 assoc.tumblr.com/post/51302471844/the-misunderstood-singleton
에릭 Engheim

29
@AdamSmith : 실제로 읽어나요 어떤 이 답변을? 귀하의 질문은 처음 두 단락에서 답변됩니다. 싱글 톤 패턴! == 단일 인스턴스.
Aaronaught

5
@Cawas and Adam Smith-링크 읽기이 답변을 읽지 않은 느낌이 들었습니다. 모든 것이 이미 있습니다.
Wilbert

19
@Cawas 나는이 답변의 고기가 싱글 인스턴스와 싱글 톤의 구별이라고 생각합니다. 싱글턴은 나쁘고 싱글 인스턴스는 그렇지 않습니다. 의존성 주입은 싱글 톤을 사용하지 않고도 단일 인스턴스를 사용하는 좋은 일반적인 방법입니다.
Wilbert

48

당신이주는 경우, 싱글 톤을 사용하는 것이 문제가 아니라 문제의 증상 -더 큰 구조적 문제 인 것처럼 들립니다 .

화면에서 캐시 개체에 데이터를 쿼리하는 이유는 무엇입니까? 캐싱은 클라이언트에게 투명해야합니다. 데이터를 제공하기위한 적절한 추상화가 있어야하며, 추상화의 구현은 캐싱을 활용할 수 있습니다.

문제는 시스템 부분 간의 종속성이 올바르게 설정되지 않았을 가능성이 높으며 이는 아마도 체계적인 것일 수 있습니다.

화면이 데이터를 얻는 위치를 알아야하는 이유는 무엇입니까? 화면에 데이터 요청을 수행 할 수있는 객체가 제공되지 않는 이유는 무엇입니까 (캐시가 숨겨져 있음)? 종종 화면 작성에 대한 책임이 중앙 집중식이 아니므로 종속성을 주입 할 명확한 시점이 없습니다.

다시, 우리는 대규모 건축 및 디자인 문제를 검토하고 있습니다.

또한 객체 의 수명 이 객체의 사용 방법과 완전히 분리 될 수 있음 을 이해하는 것이 매우 중요 합니다.

캐시는 응용 프로그램의 수명 동안 유용해야하며 (유용하기 위해) 개체의 수명은 싱글 톤의 수명입니다.

그러나 Singleton (적어도 정적 클래스 / 속성으로 Singleton의 일반적인 구현)의 문제는 그것을 사용하는 다른 클래스가 찾는 방법입니다.

정적 Singleton 구현의 경우 규칙은 필요할 때마다이를 사용하는 것입니다. 그러나 그것은 의존성을 완전히 숨기고 두 클래스를 밀접하게 결합시킵니다.

우리 가 클래스에 대한 의존성을 제공 한다면 , 그 의존성은 명백하고 소비하는 모든 클래스가 사용할 수있는 계약에 대해 알아야합니다.


2
특정 화면에 필요한 엄청난 양의 데이터가 있지만 반드시 필요한 것은 아닙니다. 그리고 이것을 정의하는 사용자 조치가 취해질 때까지 알지 못하며 많은 조합이 있습니다. 따라서 클라이언트에서 캐시되고 동기화 된 일반적인 전역 데이터 (대부분 로그인시 획득)를 유지 한 다음 후속 요청에서 캐시를 더 많이 빌드했습니다. 명시 적으로 요청 된 데이터는 같은 세션. 서버에 대한 요청을 줄이는 데 중점을 두므로 클라이언트 측 캐시가 필요합니다. <cont>
바비 테이블

1
<cont> 본질적으로 투명합니다. 특정 필수 데이터가 아직 캐시되지 않은 경우 서버에서 콜백이 발생한다는 의미입니다. 그러나 해당 캐시 관리자의 구현 (논리적 및 물리적)은 싱글 톤입니다.
Bobby Tables

6
나는 qstarin을 사용하고 있습니다. 데이터에 액세스하는 객체는 데이터가 캐시되어 있음을 알아야합니다 (또는 구현 세부 사항). 데이터 사용자는 단순히 데이터를 요청하거나 데이터를 검색 할 인터페이스를 요청합니다.
Martin York

1
캐싱은 본질적으로 구현 세부 사항입니다. 데이터를 쿼리하는 인터페이스가 있으며 데이터를 가져 오는 객체는 캐시에서 온 것인지 알 수 없습니다. 그러나이 캐시 관리자 아래에는 싱글 톤이 있습니다.
Bobby Tables

2
@Bobby Tables : 그렇다면 당신의 상황은 생각보다 끔찍하지 않습니다. 그 싱글 톤 (앱이있는 한 인스턴스가있는 객체가 아닌 정적 클래스를 의미한다고 가정)은 여전히 ​​문제가 있습니다. 데이터 제공 객체가 캐시 공급자에 의존한다는 사실을 숨기고 있습니다. 그것이 명시적이고 외부화 된 것이 좋습니다. 그것들을 분리하십시오. 그것은이 필수적 쉽게 구성 요소를 대체 할 수있는 시험 가능성에 대한, 그리고 캐시 공급자가 있습니다 프라임 이러한 구성 요소의 예 (ASP.Net에 의해 백업 캐시 공급자가 얼마나 자주).
quentin-starin

45

나는 이 질문에 대한 전체 장 을 썼습니다 . 대부분 게임의 맥락에서 볼 수 있지만 대부분 게임 외부에 적용해야합니다.

tl; dr :

Gang of Four Singleton 패턴은 두 가지 작업을 수행합니다. 어느 곳에서나 객체에 편리하게 액세스 할 수 있으며 하나의 인스턴스 만 만들 수 있습니다. 시간의 99 %, 당신이 걱정하는 것은 그것의 상반기이며, 후반을 따라 카트를 타면 불필요한 제한이 추가됩니다.

뿐만 아니라 편리한 액세스를 제공하는 더 나은 솔루션이 있습니다. 객체를 전역으로 만드는 것은이를 해결하기위한 핵 옵션이며 캡슐화를 쉽게 파괴 할 수있는 방법입니다. 글로벌에 대해 나쁜 모든 것은 싱글 톤에 완전히 적용됩니다.

동일한 객체를 터치해야하는 코드가 많이 있기 때문에 사용하는 경우 전체 코드베이스에 노출시키지 않고 해당 객체 에만 더 좋은 방법을 찾아보십시오 . 다른 솔루션 :

  • 완전히 버리십시오. 나는 상태가 없으며 헬퍼 함수의 가방 인 많은 싱글 톤 클래스를 보았습니다. 그것들은 전혀 인스턴스가 필요하지 않습니다. 정적 함수로 만들거나 함수가 인수로 사용하는 클래스 중 하나로 이동하십시오. Math할 수 있다면 특별한 수업이 필요 하지 않습니다 123.Abs().

  • 통과시켜 메소드에 다른 객체가 필요한 경우 간단한 해결책은 그냥 전달하는 것입니다. 일부 객체를 전달하는 데 아무런 문제가 없습니다.

  • 기본 수업에 넣습니다. 모든 특수 객체에 액세스해야하는 클래스가 많고 기본 클래스를 공유하는 경우 해당 객체를베이스의 멤버로 만들 수 있습니다. 그것을 만들 때 객체를 전달하십시오. 이제 파생 된 객체는 필요할 때 얻을 수 있습니다. 보호하면 개체가 계속 캡슐화 된 상태로 유지됩니다.


1
여기서 요약 할 수 있습니까?
Nicole

1
완료했지만 여전히 전체 장을 읽으십시오.
munificent

4
다른 대안 : 의존성 주입!
Brad Cupit

1
@BradCupit 그는 링크에서도 그것에 대해 이야기합니다 ... 나는 여전히 모든 것을 소화하려고한다고 말해야합니다. 그러나 내가 읽은 싱글 톤에서 가장 분명하게 읽었습니다. 지금까지는 글로벌 변수와 마찬가지로 긍정적 인 싱글 톤이 필요 했으며 Toolbox를 홍보 하고 있었습니다 . 이제는 더 이상 몰라요. Mr munificient 서비스 로케이터가 정적 도구 상자 인지 말해 줄 수 있습니까? 싱글 톤 (따라서 도구 상자)으로 만드는 것이 낫지 않습니까?
cregox

1
"Toolbox"는 "Service Locator"와 매우 유사합니다. 정적을 사용하든 단독으로 사용하든간에 대부분의 프로그램에서 그렇게 중요하지 않다고 생각합니다. 왜 정적 초기화에 의존하는 경향이 있습니다. 왜냐하면 왜 초기화가 필요없고 힙 할당을 처리해야합니까?
munificent

21

그 자체가 문제인 글로벌 상태는 아닙니다.

정말 걱정할 필요가 있습니다 global mutable state. 일정한 상태는 부수적 인 영향을받지 않으므로 문제가 적습니다.

싱글 톤의 주요 관심사는 커플 링을 추가하여 테스트와 같은 것을 어렵게 만든다는 것입니다. 다른 소스 (예 : 공장)에서 싱글 톤을 가져와 커플 링을 줄일 수 있습니다. 이렇게하면 특정 인스턴스에서 코드를 분리 할 수 ​​있습니다 (공장에 더 연결되어 있지만 적어도 공장은 다른 단계에 대한 대체 구현을 가질 수 있음).

귀하의 상황에서 싱글 톤이 실제로 인터페이스를 구현하는 한 (다른 상황에서 대안을 사용할 수있는 한) 벗어날 수 있다고 생각합니다.

그러나 싱글 톤의 또 다른 주요 단점은 일단 코드에서 코드를 제거하고 다른 것으로 대체하면 실제로 어려운 작업이된다는 것입니다 (다시 결합해야 함).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

말이 되네요 이것은 또한 내가 싱글 톤을 실제로 학대하지는 않았지만 그 사용을 의심하기 시작했습니다. 그러나이 점에 따르면 내가 한 명백한 학대는 생각할 수 없습니다. :)
Bobby Tables

2
(당신은 아마 의미 자체가 없다 "말 당")
nohat

4
@nohat : 나는 "Queens English"의 원어민이므로, 우리가 더 나아지지 않는 한 (우리 le weekend의 하나 인 oops 와 같이) 프랑스어로 보이는 것을 거부 합니다. 감사합니다 :-)
Martin York

21
그 자체 는 라틴어입니다.
아논.

2
@Anon : 알겠습니다. 그것은 그렇게 나쁘지 않다 ;-)
Martin York

19

그리고 뭐? 아무도 말하지 않았기 때문에 : Toolbox . 전역 변수 가 필요한 경우 입니다.

다른 각도에서 문제를 살펴보면 싱글 톤 남용 을 피할 수 있습니다. 응용 프로그램에 클래스의 인스턴스가 하나만 필요하고 응용 프로그램이 시작시 해당 클래스를 구성한다고 가정 해 봅시다. 클래스 자체가 단일 클래스를 담당해야하는 이유는 무엇입니까? 응용 프로그램에 이러한 종류의 동작이 필요하기 때문에 응용 프로그램에서이 책임을 맡는 것이 상당히 논리적 인 것 같습니다. 구성 요소가 아닌 응용 프로그램은 싱글 톤이어야합니다. 그런 다음 응용 프로그램은 구성 요소의 인스턴스를 응용 프로그램 별 코드에서 사용할 수있게합니다. 응용 프로그램에서 이러한 구성 요소를 여러 개 사용하면 도구 상자라는 구성 요소로 집계 할 수 있습니다.

간단히 말해서, 응용 프로그램의 도구 상자는 자체 구성 또는 응용 프로그램의 시작 메커니즘으로 구성 할 수있는 단일 요소입니다 ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

하지만 그거 알아? 싱글 톤입니다!

그리고 싱글 톤은 무엇입니까?

어쩌면 그것은 혼란이 시작되는 곳입니다.

나에게 싱글 톤 은 항상 단일 인스턴스 만 갖도록 강요 된 객체입니다. 인스턴스화 할 필요없이 언제 어디서나 액세스 할 수 있습니다. 이것이와 밀접한 관련이있는 이유 static입니다. 비교를 static위해 인스턴스가 아닌 것을 제외하고는 기본적으로 동일합니다. 우리는 그것을 자동으로 할당하기 때문에 인스턴스화 할 필요가 없습니다. 그리고 그것은 문제를 일으킬 수 있습니다.

내 경험상 staticSingleton을 간단히 교체하면 내가 사용 하고있는 중간 크기의 패치 워크 백 프로젝트에서 많은 문제가 해결되었습니다. 그것은 나쁜 디자인 프로젝트에 약간의 사용법이 있다는 것을 의미합니다. 너무 많이 있다고 생각 논의 경우 싱글 톤 패턴 입니다 유용 여부 그것은 실제로 있다면 정말 주장 할 수없는 나쁜 . 그러나 여전히 정적 방법보다 싱글 톤에 유리한 좋은 주장 이 여전히 있습니다 .

내가 확신하는 유일한 것은 싱글 톤에 대해 나쁜 것입니다. 좋은 습관을 무시하면서 싱글 톤을 사용할 때입니다. 그것은 실제로 다루기가 쉽지 않은 것입니다. 그러나 모든 패턴에 나쁜 관행을 적용 할 수 있습니다. 그리고, 나는 그것을 말하기에는 너무 일반적입니다 ... 나는 그것에 너무 많은 것이 있다는 것을 의미합니다.

내가 틀리지 마!

간단히 말해,처럼 글로벌 바르 , 싱글은해야 여전히 항상 피할 수 . 특히 그들이 남용되기 때문에. 그러나 전역 변수를 항상 피할 수는 없으며 마지막 경우에 사용해야합니다.

어쨌든, 도구 상자 이외에 다른 많은 제안이 있으며 도구 상자와 마찬가지로 각 도구에는 응용 프로그램이 있습니다 ...

다른 대안들

  • 난 그냥 싱글에 대해 읽은 최고의 기사는 제안 서비스 로케이터를 대안으로. 나에게 그것은 기본적으로 " 정적 도구 상자 "입니다. 다시 말해, 서비스 로케이터를 싱글 톤으로 만들면 도구 상자가 있습니다. 그것은 물론 싱글 톤을 피하겠다는 최초의 제안과 반대되지만, 싱글 톤의 문제를 강제하는 것은 패턴 자체가 아니라 그것이 어떻게 사용되는지에 달려 있습니다.

  • 다른 사람들공장 패턴 을 대안으로 제안 합니다. 그것은 동료로부터 들었던 첫 번째 대안이었고 우리는 전역 변수 로 사용하기 위해 신속하게 제거했습니다 . 물론 사용법도 있지만 싱글 톤도 마찬가지입니다.

위의 두 가지 대안 모두 좋은 대안입니다. 그러나 그것은 모두 당신의 사용법에 달려 있습니다.

이제 모든 비용으로 싱글 톤을 피해야한다는 것은 잘못된 것입니다 ...

  • Aaronaught 의 대답은 여러 가지 이유로 싱글 톤을 사용하지 말 것을 제안하고 있습니다 . 그러나 그것들은 패턴 자체에 직접적으로 영향을 미치지 않고, 악용되고 악용되는 방식에 대한 모든 이유입니다. 나는 그 점에 대한 모든 걱정에 동의합니다. 어떻게 할 수 없습니까? 오해의 소지가 있다고 생각합니다.

무능력 (추상화 또는 서브 클래스)은 실제로 존재하지만 무엇입니까? 그런 의미가 아닙니다. 더 없다 인터페이스 할 수없는 까지로, 내가 말할 수있는가 . 높은 커플 링 도있을 수 있지만 일반적으로 사용되는 방식 때문입니다. 필요하지 않습니다 . 실제로 커플 링 자체는 싱글 톤 패턴과 관련이 없습니다. 명확하게 설명하면 이미 테스트하기가 어렵습니다. 병렬화의 어려움은 언어와 플랫폼에 따라 다르므로 패턴에 문제가 없습니다.

실용적인 예

나는 종종 찬성하거나 싱글 톤에 대해 2가 사용되는 것을 본다. 웹 캐시 (내 경우) 및 로그 서비스 .

로깅은 일부 사람들이 주장 할 것이므로 완벽한 싱글 톤 예제입니다.

  • 요청자는 요청을 로그에 보낼 잘 알려진 객체가 필요합니다. 이것은 글로벌 액세스 지점을 의미합니다.
  • 로깅 서비스는 여러 리스너가 등록 할 수있는 단일 이벤트 소스이므로 하나의 인스턴스 만 있으면됩니다.
  • 응용 프로그램마다 다른 출력 장치에 기록 할 수 있지만 리스너를 등록하는 방식은 항상 동일합니다. 모든 사용자 정의는 리스너를 통해 수행됩니다. 클라이언트는 텍스트의 로그 방법 또는 위치를 모르고 로깅을 요청할 수 있습니다. 따라서 모든 응용 프로그램은 로깅 서비스를 정확히 같은 방식으로 사용합니다.
  • 모든 응용 프로그램은 로깅 서비스 인스턴스를 하나만 사용하여 벗어날 수 있어야합니다.
  • 재사용 가능한 구성 요소를 포함하여 모든 객체가 로깅 요청자가 될 수 있으므로 특정 응용 프로그램에 연결해서는 안됩니다.

다른 사람들은 결국 로그 서비스가 실제로는 하나의 인스턴스가 아니라는 사실을 깨닫고 나면 로그 서비스를 확장하기가 어렵다고 주장합니다.

글쎄, 나는 두 주장이 모두 유효하다고 말한다. 여기서도 문제는 싱글 톤 패턴이 아닙니다. 리팩토링이 실행 가능한 위험이라면 아키텍처 결정과 가중치에 있습니다. 일반적으로 리팩토링이 마지막으로 필요한 수정 조치 인 경우 추가 문제입니다.


감사합니다! 싱글 톤 사용에 대한 경고를 추가하기 위해 답변을 편집하려고 생각했습니다 ... 견적이 완벽하게 맞습니다!
cregox

2
당신이 그것을 좋아 기쁘다. 이것이 다운 보트를 피하는 데 도움이 될지 확신 할 수는 없지만, 아마도 독자들은이 글에서 제시된 구체적인 문제와 특히 사전 답변에서 제공된 훌륭한 분석에 비추어 볼 때이 문제에 대해 지적한 점을 연결하는 데 어려움을 겪고있을 것입니다
gnat

@ gnat 그래, 나는 이것이 긴 전투라는 것을 알고 있었다. 시간이 말 해주길 바랍니다. ;-)
cregox

1
나는 당신의 일반적인 방향에 동의하지만, 매우 베어 본 IoC 컨테이너 (예 : Funq 와 같은 더 기본적 인 것)보다 많지 않은 라이브러리에 대해서는 다소 열정적 입니다. Service Locator는 실제로 반 패턴입니다. IoC 컨테이너를 올바르게 사용하기 위해 모든 것을 리팩토링하기에는 너무 비싸기 때문에 "가난한 DI"와 함께 레거시 / 브라운 필드 프로젝트에서 주로 유용한 도구입니다.
Aaronaught

1
@Cawas : 아, 죄송합니다 java.awt.Toolkit. 내 요점은 동일 Toolbox합니다. 단일 목적의 일관된 클래스가 아니라 관련없는 비트와 조각으로 가득 찬 것처럼 들립니다. 나에게 좋은 디자인처럼 들리지 않습니다. (당신이 참조하는 문서 의존성 주입하기 전에, 2001 년부터이며, DI 컨테이너가 일반화가되었다합니다.)
존 소총을

5

싱글 톤 디자인 패턴의 주된 문제는 어플리케이션에 적합한 단위 테스트를 작성하는 것이 매우 어렵다는 것입니다.

이 "관리자"에 종속 된 모든 구성 요소는 해당 단일 인스턴스를 쿼리하여 수행합니다. 이러한 구성 요소에 대한 단위 테스트를 작성하려면이 싱글 톤 인스턴스에 데이터를 주입해야하므로 쉽지 않을 수 있습니다.

반면에 "관리자"가 생성자 매개 변수를 통해 종속 구성 요소에 주입되고 구성 요소가 관리자의 구체적인 유형, 인터페이스 또는 관리자가 구현하는 추상 기본 클래스 만 모르는 경우 테스트는 종속성을 테스트 할 때 관리자의 대체 구현을 제공 할 수 있습니다.

IOC 컨테이너를 사용하여 애플리케이션을 구성하는 구성 요소를 구성하고 인스턴스화하는 경우 하나의 "관리자"인스턴스 만 작성하도록 IOC 컨테이너를 쉽게 구성 할 수 있으므로 글로벌 애플리케이션 캐시를 제어하는 ​​동일한 단일 인스턴스 만 달성 할 수 있습니다. .

그러나 단위 테스트에 신경 쓰지 않는다면 싱글 톤 디자인 패턴이 완벽 할 수 있습니다. (그러나 나는 그것을하지 않을 것입니다)


멋지게이 가장 테스트 싱글 톤에 대한 문제를 설명하는 답변입니다 설명
호세의 Tomás Tocino

4

디자인 컴퓨팅이 좋거나 나쁠 수 있다는 점 에서 싱글 톤은 근본적으로 나쁘지 않습니다. 정확해야합니다 (예상 결과 제공). 코드를 더 명확하거나 효율적으로 만들면 유용하거나 유용하지 않을 수 있습니다.

싱글 톤이 유용한 한 가지 경우는 실제로 고유 한 엔티티를 나타내는 경우입니다. 대부분의 환경에서 데이터베이스는 고유하며 실제로 하나의 데이터베이스 만 있습니다. 해당 데이터베이스에 연결하는 것은 특별한 권한이 필요하거나 여러 연결 유형을 통과하기 때문에 복잡 할 수 있습니다. 단일 연결로 연결을 구성하는 것은 아마도 이런 이유로 많은 의미가 있습니다.

그러나 단일 톤이 실제로 단일 변수이며 전역 변수가 아닌지 확인해야합니다. 이는 하나의 고유 한 데이터베이스가 실제로 4 개의 데이터베이스 (생산, 스테이징, 개발 및 테스트 설비 각각에 대해 하나씩) 인 경우에 중요합니다. Database Singleton은 연결해야 할 인스턴스를 파악하고 해당 데이터베이스의 단일 인스턴스를 잡고 필요한 경우 연결 한 다음 호출자에게 반환합니다.

싱글 톤이 실제로 싱글 톤이 아닌 경우 (대부분의 프로그래머가 화를 낼 때), 느리게 인스턴스화 된 전역이므로 올바른 인스턴스를 주입 할 기회가 없습니다.

잘 설계된 싱글 톤 패턴의 또 다른 유용한 기능은 종종 관찰 할 수 없다는 것입니다. 발신자가 연결을 요청합니다. 이를 제공하는 서비스는 풀링 된 객체를 반환하거나 테스트를 수행하는 경우 모든 호출자에 대해 새 객체를 만들거나 대신 모의 객체를 제공 할 수 있습니다.


3

실제 객체를 나타내는 싱글 톤 패턴을 사용하는 것은 완벽합니다. 나는 iPhone을 위해 쓰고 Cocoa Touch 프레임 워크에는 많은 싱글 톤이있다. 응용 프로그램 자체는 클래스의 싱글 톤으로 표시됩니다 UIApplication. 하나의 응용 프로그램 만 있으므로 싱글 톤으로 표시하는 것이 적절합니다.

싱글 톤을 데이터 관리자 클래스로 사용하면 올바르게 설계된 한 괜찮습니다. 데이터 속성의 버킷이라면 전역 범위보다 낫지 않습니다. 그것이 게터와 세터의 집합이라면, 그것은 좋지만 여전히 좋지는 않습니다. 원격 데이터 가져 오기, 캐싱, 설정 및 해제 등 데이터에 대한 모든 인터페이스를 실제로 관리하는 클래스라면 매우 유용 할 수 있습니다.


2

싱글 톤은 서비스 지향 아키텍처를 프로그램에 투영 한 것입니다.

API는 프로토콜 수준에서 싱글 톤의 예입니다. 본질적으로 싱글 톤을 통해 Twitter, Google 등에 액세스합니다. 그렇다면 왜 프로그램 내에서 싱글 톤이 나빠지 는가?

프로그램을 어떻게 생각 하느냐에 달려 있습니다. 프로그램을 무작위로 묶인 캐시 된 인스턴스가 아닌 서비스 사회로 생각하면 싱글 톤이 완벽하게 이해됩니다.

싱글 톤은 서비스 액세스 포인트입니다. 매우 복잡한 내부 아키텍처를 숨길 수있는 밀접하게 바인딩 된 기능 라이브러리에 대한 공용 인터페이스입니다.

그래서 공장과 다른 싱글 톤을 보지 못합니다. 싱글 톤에는 생성자 매개 변수가 전달 될 수 있습니다. 예를 들어 가능한 모든 선택 메커니즘에 대해 기본 프린터를 해결하는 방법을 알고있는 일부 컨텍스트에서 작성할 수 있습니다. 테스트를 위해 자신의 모형을 삽입 할 수 있습니다. 따라서 매우 유연 할 수 있습니다.

이 키는 프로그램 내부에 있으며 약간의 기능이 필요할 때 서비스가 작동하고 사용할 준비가되었음을 확신하면서 싱글 톤에 액세스 할 수 있습니다. 준비 상태로 간주되기 위해 상태 시스템을 거쳐야하는 프로세스에서 다른 스레드가 시작될 때이 키가 중요합니다.

일반적으로 XxxServiceclass 주위를 싱글 톤으로 감싸는 클래스를 래핑합니다 Xxx. 싱글 톤은 전혀 수업 Xxx에 참여 하지 않고 다른 수업으로 분리됩니다 XxxService. 때문입니다 Xxx그것은 가능성이 아니라하더라도, 여러 인스턴스를 가질 수 있지만, 우리는 여전히 하나 개 갖고 싶어 Xxx각 시스템에 인스턴스가 전 세계적으로 접근. XxxService우려의 좋은 분리를 제공합니다. Xxx싱글 톤 정책을 시행 할 필요는 없지만 Xxx필요할 때 싱글 톤으로 사용할 수 있습니다 .

다음과 같은 것 :

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

첫 번째 질문은 응용 프로그램에서 많은 버그를 발견 했습니까? 캐시 업데이트를 잊어 버렸거나 캐시가 잘못되었거나 변경하기가 어렵다고 생각하십니까? (색상도 변경하지 않으면 앱이 크기를 변경하지 않았 음을 기억하지만 색상을 다시 변경하고 크기를 유지할 수 있습니다).

당신이 할 일은 그 수업을 가지고 있지만 모든 정적 회원을 제거하는 것입니다. 좋아, 이것은 nessacary가 아니지만 나는 그것을 추천한다. 실제로 클래스를 일반 클래스처럼 초기화하고 포인터를 전달하십시오. ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). andhave_no_structure ()

더 많은 작업이지만 실제로는 덜 혼동됩니다. 더 이상 전역 적이 지 않기 때문에 지금 변경할 수없는 곳도 있습니다. 모든 관리자 수업은 정규 수업이므로 처리하십시오.


1

IMO, 귀하의 예는 괜찮습니다. 다음과 같이 인수 분해하는 것이 좋습니다. 각 (및 뒤에) 데이터 객체에 대한 캐시 객체; 캐시 객체와 db 접근 자 객체는 동일한 인터페이스를 갖습니다. 이를 통해 코드 내외부에서 캐시를 교환 할 수 있습니다. 또한 쉬운 확장 경로를 제공합니다.

그래픽:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

DB 접근 자와 캐시는 동일한 객체 또는 덕 유형에서 동일한 객체처럼 보이도록 상속 할 수 있습니다. 플러그 / 컴파일 / 테스트 할 수 있고 여전히 작동하는 한.

이것은 Uber-Cache 개체를 시작하거나 수정하지 않고도 새 캐시를 추가 할 수 있도록 분리합니다. YMMV. IANAL. 기타.


1

파티에 조금 늦었지만 어쨌든.

Singleton은 다른 것과 마찬가지로 도구 상자의 도구입니다. 도구 상자에 단 하나의 망치보다 더 많은 것이 있기를 바랍니다.

이걸 고려하세요:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

vs

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

첫 번째 경우는 높은 커플 링 등을 초래합니다. 두 번째 방법은 @Aaronaught가 설명 할 수있는 한 문제가 없습니다. 사용 방법에 관한 모든 것.


동의하지 않는다. 두 번째 방법은 "더 나은"(커플 링 감소)이지만 여전히 DI로 해결되는 문제가 있습니다. 서비스 구현을 제공하기 위해 클래스 소비자에게 의존해서는 안됩니다. 클래스를 만들 때 생성자에서 더 잘 수행됩니다. 인터페이스는 인수 측면에서 최소값 만 요구하면됩니다. 또한 클래스에서 단일 인스턴스를 작동해야하는 적절한 기회가 있습니다. 다시이 규칙을 시행하기 위해 소비자에게 의존하는 것은 위험하고 불필요합니다.
AlexFoxGill

때때로 Di는 주어진 작업에 과잉입니다. 샘플 코드의 메소드는 생성자가 될 수 있지만 반드시 그럴 필요는 없습니다. 또한 DoSomething은 ISomething을 취할 수 있으며 MySingleton은 해당 인터페이스를 구현할 수 있습니다. 이는 샘플이 아니라 샘플 일뿐입니다.
Evgeni

1

각 화면을 생성자에서 Manager로 가져갑니다.

앱을 시작할 때 하나의 관리자 인스턴스를 만들어 전달합니다.

이를 Inversion of Control이라고하며 구성 변경 및 테스트시 컨트롤러를 교체 할 수 있습니다. 또한 응용 프로그램의 여러 인스턴스 또는 응용 프로그램의 일부를 병렬로 실행할 수 있습니다 (테스트에 적합합니다). 마지막으로 관리자는 소유 객체 (시작 클래스)로 죽습니다.

따라서 앱은 나무처럼 구성되며, 위의 것은 그 아래에서 사용되는 모든 것을 소유합니다. 모두가 모든 사람을 알고 전역 방법을 통해 서로를 찾는 메시와 같은 앱을 구현하지 마십시오.

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