의도

  • 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공함

 

언제 쓰는가?

  • 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점(access point)으로 사용자가 접근할 수 있도록 해야 할 때
  • 유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때

 

구조

GoF의 디자인 패턴 중 P.182

  • Singleton : Instance() 연산을 정의하여, 유일한 인스턴스로 접근할 수 있도록 함. 유일한 인스턴스를 생성하는 책임을 맡음.

 

결과

  1. 유일하게 존재하는 인스턴스로의 접근을 통제 : Singleton 클래스 자체가 인스턴스를 캡슐화 하기 때문에, 이 클래스에서 사용자가 언제, 어떻게 이 인스턴스에 접근할 수 있는지 제어할 수 있음
  2. 이름 공간을 좁힘 : 전역변수보다 더 좋음
  3. 연산 및 표현의 정제를 허용 : 싱글턴 클래스는 상속될 수 있음
  4. 인스턴스의 개수를 변경하기가 자유로움 : 인스턴스에 접근할 수 있는 허용 범위를 결정하는 연산만 변경하면 됨
  5. 클래스 연산을 사용하는 것보다 훨씬 유연한 방법 : 클래스의 정적 멤버 함수가 인스턴스를 가질 수 없고, 오버라이드가 안되는 특징과 비교하여 낫다는 뜻

 

예제 코드 ( 1 )

  • 싱글턴은 기본적으로 본인의 생성자를 감추어 외부에서 이 클래스를 사용하여 직접적인 생성을 못하게 막음
  • static 변수가 가지는 특성 중 응용 프로그램 내 단 하나만 존재하는 특성을 이용하고, 이 변수에 접근할 수 있는 접근점을 제공하는데, Instance() 혹은 Get()을 주로 사용함
  • static 변수는 프로그램이 시작하며 초기화되어 있어야 하기 때문에 cpp 파일 중 이 변수에 대한 초기화를 nullptr 혹은 0으로 하게 되고 이 싱글턴 객체에 접근하는 순간 nullptr인 것을 인지하면 그 때 첫 의미있는 초기화를 진행하게 되고 그 이후로는 nullptr이 아니기 때문에 새로 초기화를 진행하지는 않음
  • 이 예제에선 기본 구조만을 확인해보겠음
#include "SingletonBasic.h"

int main()
{
	Singleton::Instance();

	return 0;
}
  • 위와 같이 static 멤버 함수인 Instance에는 클래스의 네임스페이스를 활용하여 접근할 수 있고, 저렇게 첫 접근이 시작되는 순간 new 연산을 활용하여 Singleton 클래스 내 static 멤버 변수가 초기화 됨
//SingletonBasic.h

#pragma once

class Singleton
{
public:
	static Singleton* Instance()
	{
		if (instance == nullptr)
		{
			instance = new Singleton();
		}
		return instance;
	}

protected:
	Singleton() {}

private:
	static Singleton* instance;
};
  • 기본 구조는 위와 같음
  • 접근점을 제공하는 static 멤버 함수 Instance를 제공
  • 생성자는 protected 혹은 private 으로 감추어야 함. 조금 더 정확히 하려면 복사 생성자나 이동 생성자의 경우도 delete 키워드 혹은 private으로 감추어야 함
  • static 멤버 변수로 instance를 가질 수도 있고 Instance() 메서드 내 static 지역 변수로서 사용하여도 무방. 단, static 멤버 변수(클래스 내 static 변수)로 사용하게 되면 프로그램 실행 시 초기화가 되어 있어야 하므로 cpp 파일에서 nullptr로 초기화를 해주어야 함
//SingletonBasic.cpp

#include "SingletonBasic.h"

Singleton* Singleton::instance = nullptr;

 

예제 코드 ( 2 )

  • 싱글턴으로 만드는 귀찮은 작업을 생략할 수 있게 끔 싱글턴으로 만들고자 하는 클래스들은 상속을 이용하여 구현하고자 함
#include "Singleton1.h"

int main()
{
	AudioSystem::Get()->Print();

	return 0;
}
  • 싱글턴으로서 사용되는 AudioSystem. 사용하는 데에 있어 1번 예제와 다를 바 없음.
//Singleton1.h

#pragma once
#include <iostream>

template<typename T>
class Singleton
{
public:
	static T* Get()
	{
		if (instance == nullptr)
		{
			instance = new T;
		}
		return instance;
	}

	static void Delete()
	{
		delete instance;
		instance = nullptr;
	}

protected:
	Singleton() {}

private:
	static T* instance;
};

template<typename T>
T* Singleton<T>::instance = nullptr;

class AudioSystem : public Singleton<AudioSystem>
{
	friend class Singleton<AudioSystem>;

private:
	AudioSystem() {}

public:
	void Print() { std::cout << "temp : " << temp << std::endl; }

private:
	int temp = 10;
};
  • 싱글턴으로 만들고자 하는 클래스가 있다면 위 Singleton 클래스를 상속하여 템플릿 인자에는 자신의 클래스를 넣어주고, friend 선언을 통해 상속해 준 객체에서 자신의 생성자에 접근할 수 있게 끔 해주어야 함
  • Singleton 클래스는 템플릿 클래스로 선언되어 외부에서 타입 캐스트 없이 사용할 수 있게 끔 Get()의 반환값과 instance의 타입을 T로 선언함
  • 또한 static 멤버 변수인 instance에 대한 초기화가 진행되는 장소는 cpp가 아닌 h 파일로서 템플릿의 특성 상 cpp 파일에서는 초기화 할 수 없음 ( T가 어떤 값으로 넘어올 지 모르기 때문 )

'디자인 패턴 > 생성' 카테고리의 다른 글

원형(Prototype)  (0) 2023.12.19
팩토리 메서드(Factory Method)  (2) 2023.12.18
빌더(Builder)  (2) 2023.12.17
추상 팩토리(Abstract Factory)  (0) 2023.12.15
생성 패턴  (0) 2023.12.15

+ Recent posts