싱글 톤 패턴의 대안


27

싱글 톤 패턴에 대한 다른 의견을 읽었습니다. 어떤 사람들은 모든 비용으로 피해야하고 어떤 상황에서는 유용 할 수 있다고 유지합니다.

단일 클래스를 사용하는 한 가지 상황은 특정 클래스 A의 객체를 만들기 위해 팩토리 (F 유형의 객체 f라고 가정 함)가 필요한 경우입니다. 팩토리는 일부 구성 매개 변수를 사용하여 한 번 생성 된 다음 객체가 생성 될 때마다 사용됩니다 유형 A가 인스턴스화됩니다. 따라서 A를 인스턴스화하려는 코드의 모든 부분이 싱글 톤 f를 가져오고 새 인스턴스를 만듭니다.

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

그래서 일반적인 시나리오는

  1. 최적화 이유 (여러 팩토리 객체가 필요하지 않음) 또는 공통 상태 공유를 위해 클래스의 인스턴스가 하나만 필요합니다 (예 : 팩토리는 여전히 A 인스턴스를 얼마나 만들 수 있는지 알고 있습니다)
  2. 코드의 다른 곳에서 F의 F 인스턴스에 액세스 할 수있는 방법이 필요합니다.

내가 관심이없는 이 패턴이 좋은지 나쁜지 여부를 해당 항목에서 설명합니다,하지만 난 싱글 톤을 사용하지 않도록 할 가정, 어떤 다른 패턴 내가 사용할 수 있습니까?

내가 가진 아이디어는 (1) 레지스트리에서 팩토리 객체를 가져 오거나 (2) 프로그램 시작 중 어느 시점에서 팩토리를 만든 다음 팩토리를 매개 변수로 전달하는 것이 었습니다.

솔루션 (1)에서 레지스트리 자체는 싱글 톤이므로 공장에서 레지스트리로 싱글 톤을 사용하지 않는 문제를 방금 변경했습니다.

경우 (2) 공장 객체가 제공되는 초기 소스 (객체)가 필요하므로 다른 싱글 톤 (공장 인스턴스를 제공하는 객체)으로 다시 넘어 갈까 두렵습니다. 이 싱글 톤 체인을 따라 가면 다른 싱글 톤 을 직접 또는 간접적으로 관리 하는 단일 싱글 톤 (전체 응용 프로그램)으로 문제를 줄일 수 있습니다 .

이 마지막 옵션 (다른 모든 고유 한 객체를 생성하고 올바른 장소에 다른 모든 싱글 톤을 주입하는 하나의 초기 싱글 톤 사용)이 수용 가능한 솔루션입니까? 이것이 싱글 톤을 사용하지 말 것을 권고 할 때 암시 적으로 제안되는 솔루션입니까, 아니면 예를 들어 위에서 설명한 예에서 다른 솔루션은 무엇입니까?

편집하다

내 질문의 요점을 일부 사람들이 오해했다고 생각하기 때문에 여기에 더 많은 정보가 있습니다. 여기 에서 설명 된 바와 같이 , 싱글 톤 이라는 단어 는 (a) 단일 인스턴스 객체를 갖는 클래스 및 (b) 그러한 객체를 생성하고 액세스하는데 사용되는 디자인 패턴을 나타낼 수있다.

보다 명확하게하기 위해 (a)에 고유 한 객체 라는 용어를 사용하고 (b)에 싱글 톤 패턴 을 사용하겠습니다 . 그래서, 싱글 톤 패턴과 의존성 주입이 무엇인지 알고 있습니다 (BTW, 요즘 DI를 사용하여 일부 코드에서 싱글 톤 패턴의 인스턴스를 제거했습니다).

내 요점은 전체 객체 그래프가 main 메소드의 스택에있는 단일 객체에서 인스턴스화되지 않는 한 항상 싱글 톤 패턴을 통해 일부 고유 객체에 액세스해야한다는 것입니다.

내 질문은 완전한 객체 그래프 생성 및 배선이 주요 방법에 의존하는지 (예 : 패턴 자체를 사용하지 않는 강력한 DI 프레임 워크를 통해) 유일한 싱글 톤 패턴 프리 솔루션인지 여부입니다.


8
의존성 주입 ...
팔콘

1
@ 팔콘 : 의존성 주입 ... 무엇? DI는이 문제를 해결하는 데 아무 도움이되지 않지만 종종 숨기는 편리한 방법을 제공합니다.
pdr

@Falcon IoC 컨테이너를 의미합니까? 이를 통해 한 줄로 종속성을 해결할 수 있습니다. 예 : Dependency.Resolve <IFoo> (); 또는 Dependency.Resolve <IFoo> (). DoSomething ();
CodeART

이것은 꽤 오래된 질문이며 이미 대답했습니다. 나는 아직도 이것을 연결하는 것이 좋을 것이다 : stackoverflow.com/questions/162042/…
TheSilverBullet

답변:


7

두 번째 옵션은 좋은 방법입니다. 종종 의존성 주입입니다. 이것은 싱글 톤과 전역 변수를 피하려고 할 때 프로그램 전체에서 상태를 공유하는 데 사용되는 패턴입니다.

무언가 가 공장을 만들어야 한다는 사실을 피할 수 없습니다 . 그런 것이 응용 프로그램 인 경우에도 마찬가지입니다. 중요한 점은 팩토리가 어떤 객체를 생성했는지 신경 쓰지 않아야하며 팩토리를받는 객체는 팩토리의 위치에 의존해서는 안된다는 것입니다. 객체에 응용 프로그램 싱글 톤에 대한 포인터를 가져 와서 공장에 요청하지 마십시오. 응용 프로그램이 팩토리를 작성하여 필요한 오브젝트에 제공하도록하십시오.


17

싱글 톤의 목적은 특정 영역 내에 하나의 인스턴스 만 존재할 수 있도록하는 것입니다. 이것은 싱글 톤 동작을 강제해야하는 강력한 이유가있는 경우 싱글 톤이 유용하다는 것을 의미합니다. 실제로, 이것은 거의 사실이 아니며, 멀티 프로세싱과 멀티 스레딩은 확실히 '고유 한'의 의미를 흐리게합니다. 그것은 프로세스 당, 프로세스 당, 스레드 당, 요청 당 하나의 인스턴스입니까? 그리고 싱글 톤 구현이 경쟁 조건을 처리합니까?

싱글 톤 대신 다음 중 하나를 사용하는 것이 좋습니다.

  • 예를 들어 팩토리와 같은 수명이 짧은 로컬 인스턴스 : 일반적인 팩토리 클래스는 최소한의 상태를 가지고 있으며, 목적을 달성 한 후에도이를 유지할 실제적인 이유는 없습니다. 클래스를 생성하고 삭제하는 오버 헤드는 모든 실제 시나리오의 99 %에서 걱정할 필요가 없습니다.
  • 예를 들어 리소스 관리자 등을위한 인스턴스 전달 몇 달 동안 두 번째 자원 관리자를 두는 것이 합리적 일 것입니다 ...

그 이유는 싱글 톤이 위장 된 전역 상태이기 때문에 애플리케이션 전체에 높은 수준의 커플 링이 도입된다는 의미입니다. 코드의 어느 부분이든 최소한의 노력으로 어디서나 싱글 톤 인스턴스를 가져올 수 있습니다. 로컬 객체를 사용하거나 인스턴스를 전달하는 경우 훨씬 더 많은 제어권을 가지며 범위를 작게 유지하고 종속성을 좁힐 수 있습니다.


3
이것은 매우 좋은 대답이지만 싱글 톤 패턴을 사용해야하는 이유는 무엇인지 묻지 않았습니다. 나는 세계 국가와 관련된 문제를 안다. 어쨌든, 문제의 주제는 고유 한 객체를 사용하는 방법과 단일 패턴이없는 구현 방법이었습니다.
조르지오

3

당신을 포함한 대부분의 사람들은 싱글 톤 패턴이 실제로 무엇인지 완전히 이해하지 못합니다. Singleton 패턴은 클래스의 인스턴스 하나만 존재한다는 것을 의미하며 애플리케이션 전체에서 해당 인스턴스에 대한 참조를 가져 오는 코드 메커니즘이 있습니다.

GoF 책에서 정적 필드에 대한 참조를 반환하는 정적 팩토리 메소드는 해당 메커니즘의 모습과 심각한 단점이 있는 예일뿐입니다 . 불행하게도, 모든 사람과 그들의 개는 그 메커니즘에 걸렸고 그것이 싱글 톤이 전부라고 생각했습니다.

당신이 인용하는 대안은 실제로 참조를 얻기위한 다른 메커니즘을 가진 싱글 톤입니다. (2) 당신이 호출 스택의 루트 근처의 몇 곳에서만 참조가 필요하지 않으면 분명히 너무 많이지나갑니다. (1) 조잡한 의존성 주입 프레임 워크처럼 들리므로 왜 사용하지 않습니까?

기존 DI 프레임 워크는 유연하고 강력하며 잘 테스트되었다는 이점이 있습니다. 그들은 싱글 톤을 관리하는 것 이상을 할 수 있습니다. 그러나 기능을 완전히 사용하려면 항상 가능한 것은 아니지만 특정 방식으로 응용 프로그램을 구조화하는 것이 가장 효과적입니다. 이상적으로는 DI 프레임 워크를 통해 수집되고 모든 종속성이 전 이적으로 채워져있는 중앙 객체가 있습니다. 그런 다음 실행되었습니다.

편집 : 궁극적으로 모든 것은 어쨌든 주요 방법에 달려 있습니다. 그러나 당신이 맞습니다 : 전역 / 정적 사용을 완전히 피하는 유일한 방법은 기본 방법으로 모든 것을 설정하는 것입니다. DI는 주요 방법이 불투명하고 서버를 설정하는 서버 환경에서 가장 널리 사용되며 응용 프로그램 코드는 기본적으로 서버 코드에 의해 인스턴스화되고 호출되는 콜백으로 구성됩니다. 그러나 대부분의 DI 프레임 워크는 또한 "특별한 경우"를 위해 중앙 레지스트리 (예 : Spring의 ApplicationContext)에 직접 액세스 할 수 있습니다.

기본적으로 사람들이 지금까지 얻은 가장 좋은 것은 언급 한 두 가지 대안의 영리한 조합입니다.


기본 아이디어 의존성 주입이 기본적으로 위에서 스케치 한 첫 번째 접근 방법 (예 : 레지스트리 사용)이라는 말입니까?
조르지오

@Giorgio : DI는 항상 그러한 레지스트리를 포함하지만 더 나은 점은 객체가 필요한 곳마다 레지스트리를 쿼리하는 대신 위에 쓴 것처럼 전 이적으로 채워지는 중앙 객체가 있다는 것입니다.
Michael Borgwardt

@Giorgio : 구체적인 예제로 이해하기가 더 쉽습니다. Java EE 앱이 있다고 가정 해 봅시다. 프론트 엔드 로직은 JSF 관리 Bean의 조치 방법에 있습니다. 사용자가 버튼을 누르면 JSF 컨테이너가 관리 Bean의 인스턴스를 작성하고 조치 메소드를 호출합니다. 그러나 그 전에 DI 구성 요소는 Managed Bean 클래스의 필드를보고 특정 주석이있는 필드는 DI 구성에 따라 서비스 개체에 대한 참조로 채워지며 해당 서비스 개체는 동일한 작업을 수행합니다. . 그런 다음 조치 메소드가 서비스를 호출 할 수 있습니다.
Michael Borgwardt

일부 사람들 (귀하를 포함)은 질문을주의 깊게 읽지 않고 실제로 요청되는 내용을 오해합니다.
조르지오

1
@Giorgio : 원래의 질문에 당신이 이미 의존성 주입에 익숙하다는 것을 나타내는 것은 없습니다. 그리고 업데이트 된 답변을 참조하십시오.
Michael Borgwardt

3

몇 가지 대안이 있습니다.

의존성 주입

모든 객체는 객체가 생성 될 때 그 의존성이 전달됩니다. 일반적으로 프레임 워크 또는 소수의 팩토리 클래스가 오브젝트 작성 및 배선을 담당합니다.

서비스 레지스트리

모든 객체에는 서비스 레지스트리 객체가 전달됩니다. 다양한 서비스를 제공하는 다양한 객체를 요청하는 메소드를 제공합니다. Android 프레임 워크는이 패턴을 사용합니다.

루트 매니저

객체 그래프의 근본 인 단일 객체가 있습니다. 이 객체의 링크를 따라 가면 다른 객체를 찾을 수 있습니다. 코드는 다음과 같은 경향이 있습니다.

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

조롱 외에도 DI가 싱글 톤을 사용하는 것보다 어떤 이점이 있습니까? 이것은 매우 오랜 시간 버그를 일으킨 질문입니다. 레지스트리 및 루트 관리자는 싱글 톤 또는 DI를 통해 구현되지 않습니까? (실제로 IoC 컨테이너 레지스트리입니다.)
Konrad Rudolph

1
@KonradRudolph, DI를 사용하면 종속성이 명시 적이며 객체는 주어진 리소스가 어떻게 제공되는지에 대한 가정을하지 않습니다. 싱글 톤을 사용하면 종속성이 암시 적이며 모든 단일 사용은 단일 객체 만 존재한다고 가정합니다.
Winston Ewert

@ KonradRudolph, 다른 방법은 Singletons 또는 DI를 사용하여 구현됩니까? 예, 아니오. 결국에는 객체를 전달하거나 전역 상태로 객체를 저장해야합니다. 그러나이 작업을 수행하는 방법에는 미묘한 차이가 있습니다. 위에서 언급 한 세 가지 버전은 모두 전역 상태 사용을 피합니다. 그러나 엔드 코드가 참조를 얻는 방식은 다릅니다.
Winston Ewert

싱글 톤을 사용하면 싱글 톤이 인터페이스를 구현하거나 (또는 ​​그렇지 않은 경우) 싱글 톤 인스턴스를 사용하지 않고 Singleton::instance클라이언트 코드의 모든 곳에서 쿼리하는 대신 싱글 톤 인스턴스를 메소드에 전달합니다 .
Konrad Rudolph

@KonradRudolph, 그렇다면 실제로 하이브리드 싱글 톤 / DI 접근법을 사용하고 있습니다. 내 순수 주의자 측은 당신이 순수한 DI 접근법을 찾아야한다고 생각합니다. 그러나 싱글 톤과 관련된 대부분의 문제는 실제로 적용되지 않습니다.
Winston Ewert

1

싱글 톤의 요구를 단일 함수로 정리할 수 있다면 단순한 팩토리 함수를 사용하는 것이 어떻습니까? 전역 함수 (예제에서 클래스 F의 정적 메서드)는 본질적으로 싱글 톤이며 컴파일러와 링커에 의해 고유성이 적용됩니다.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

물론, 싱글 톤의 동작을 단일 함수 호출로 줄일 수 없을 때이 문제가 발생하지만, 관련 함수의 작은 집합이이를 방해 할 수 있습니다.

당신의 질문에있는 항목 1과 2는 당신이 정말로 무언가 중 하나를 원한다는 것을 분명히합니다. 싱글 톤 패턴의 정의에 따라 이미 사용했거나 매우 가깝습니다. 나는 그것이 싱글 톤이 아니거나 적어도 하나에 가깝지 않으면 서 무언가를 가질 수 있다고 생각하지 않습니다. 싱글 톤의 의미에 너무 가깝습니다.

당신이 제안했듯이, 어떤 시점에서 당신은 무언가 중 하나를 가져야하므로, 아마도 문제는 무언가의 단일 사례가 아니라 문제의 남용을 예방 (또는 적어도 낙담 또는 최소화)하기위한 조치를 취하는 것입니다. "싱글 톤"에서 가능한 한 많은 상태로 파라미터를 이동하는 것이 좋습니다. 싱글 톤으로 인터페이스를 좁히는 것도 도움이됩니다. 때로는 힙이나 파일 시스템과 같이 싱글 톤을 매우 견고하게 만들어야합니다.


4
개인적으로 나는 이것을 싱글 톤 패턴의 다른 구현으로 본다. 이 경우 싱글 톤 인스턴스는 클래스 자체입니다. 특히 "실제"싱글 톤과 동일한 단점이 있습니다.
Joachim Sauer

@ 랜달 쿡 : 당신의 대답이 내 요점을 포착한다고 생각합니다. 나는 싱글 톤이 매우 나쁘고 모든 비용을 피해야한다는 것을 매우 자주 읽습니다. 나는 이것에 동의하지만 반면에 나는 항상 그것을 피하는 것이 기술적으로 불가능하다고 생각합니다. "무언가 중 하나"가 필요할 때 어딘가에 생성하고 어떤 식 으로든 액세스해야합니다.
조르지오

1

C ++ 또는 Python과 같은 다중 패러다임 언어를 사용하는 경우 싱글 톤 클래스의 대안은 네임 스페이스로 묶인 함수 / 변수 세트입니다.

개념적으로 말하면, 자유 전역 변수, 자유 전역 함수 및 정보 숨기기에 사용되는 정적 변수가 모두 포함 된 C ++ 파일은 모두 네임 스페이스에 래핑되어 싱글 톤 "클래스"와 거의 동일한 효과를 제공합니다.

상속을 원할 때만 고장납니다. 나는이 방법으로 더 좋을 싱글 톤을 많이 보았습니다.


0

싱글 톤 캡슐화

사례 시나리오. 응용 프로그램은 객체 지향적이며 더 많은 클래스가 있더라도 3 또는 4 개의 특정 싱글 톤이 필요합니다.

예 전 (의사 코드와 같은 C ++) :

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

한 가지 방법은 일부 (전역) 싱글 톤을 부분적으로 제거하는 것입니다. 모든 싱글 톤은 멤버로서 다른 싱글 톤을 갖는 고유 한 싱글 톤으로 캡슐화합니다.

이것은 "여러 싱글 톤, 접근, 전혀 싱글 톤이 아닌"접근 대신에 "모든 싱글 톤을 하나의 접근으로 캡슐화"하는 방식입니다.

예제 후 (의사 코드와 같은 C ++) :

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

예제는 유사 코드와 유사하며 사소한 버그 나 구문 오류를 무시하고 질문에 대한 해결책을 고려하십시오.

사용되는 프로그래밍 언어가 싱글 톤 또는 비 싱글 톤 구현을 구현하는 방법에 영향을 줄 수 있기 때문에 고려해야 할 다른 사항도 있습니다.

건배.


0

원래 요구 사항 :

그래서 일반적인 시나리오는

  1. 최적화 이유 (여러 팩토리 객체가 필요하지 않음) 또는 공통 상태 공유를 위해 클래스의 인스턴스가 하나만 필요합니다 (예 : 팩토리는 여전히 얼마나 많은> 인스턴스를 만들 수 있는지 알고 있습니다)
  2. 코드의 다른 곳에서 F의 F 인스턴스에 액세스 할 수있는 방법이 필요합니다.

싱글 톤의 정의 (그리고 나중에 참조하는 것)와 일치하지 마십시오. GoF (내 버전은 1995) 페이지 127 :

클래스에 인스턴스가 하나만 있는지 확인하고 글로벌 액세스 지점을 제공하십시오.

당신은 단지 하나 개의 인스턴스가 필요한 경우, 그되지 않습니다 배제 더 만드는 것을.

전 세계적으로 액세스 가능한 단일 인스턴스를 원하는 경우 세계적으로 액세스 가능한 단일 인스턴스를 만드십시오 . 당신이하는 모든 일에 대해 명명 된 패턴을 가질 필요는 없습니다. 그렇습니다. 전 세계적으로 액세스 가능한 단일 인스턴스는 일반적으로 나쁩니다. 그들에게 이름을주는 것이 덜 나쁘지 는 않습니다 .

글로벌 액세스 가능성을 피하기 위해 공통적 인 '인스턴스를 만들고 전달'하거나 '두 객체의 소유자가 서로 접착'하는 방식이 효과적입니다.


0

IoC 컨테이너를 사용하는 것은 어떻습니까? 몇 가지 생각으로 다음과 같은 줄에 무언가가 생길 수 있습니다.

Dependency.Resolve<IFoo>(); 

IFoo시작시 사용할 구현을 지정해야 하지만 나중에 쉽게 교체 할 수있는 일회용 구성입니다. 해결 된 인스턴스의 수명은 일반적으로 IoC 컨테이너로 제어 할 수 있습니다.

정적 싱글 톤 보이드 방법은 다음으로 대체 될 수 있습니다.

Dependency.Resolve<IFoo>().DoSomething();

정적 싱글 톤 게터 메소드는 다음으로 대체 될 수 있습니다.

var result = Dependency.Resolve<IFoo>().GetFoo();

예는 .NET과 관련이 있지만 다른 언어로도 매우 유사하다고 확신합니다.

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