Post

싱글톤 패턴 개념 정리(강의 - 얄팍한 코딩사전)

객체지향 디자인 패턴 중 하나인 싱글톤 패턴에 대한 개념 정리 및 예제 코드

싱글톤 패턴 개념 정리(강의 - 얄팍한 코딩사전)

들어가며

소프트웨어 개발에서는 단일 인스턴스를 유지하는 것이 중요한 경우가 많다. 싱글톤(Singleton) 패턴은 이러한 상황에서 하나의 인스턴스만 생성되도록 보장하는 객체지향 디자인 패턴이다. 이 패턴은 전역 인스턴스를 제공하며, 인스턴스의 생성과 접근을 제한한다. 주로 리소스를 많이 사용하는 객체나, 시스템 전체에서 동일한 설정이나 상태를 공유할 필요가 있을 때 사용된다.

작성자 같은 경우에는 Unity Engine에서 데이터를 .json 파일로 저장하기 위한 SaveManager 구현과, GameManager에서 프로세스를 처리하기 위해 싱글톤 패턴을 공부해 본 적이 있다. 이후 학교에서 Java 수업을 통해 싱글톤 패턴의 매력에 빠지게 되었다. 객체지향 패턴 중 가장 좋아하는 패턴이 바로 싱글톤 패턴이다.

이 포스트는 ‘얄팍한 코딩사전’ 유튜브 채널의 04. 싱글톤(Singleton) 패턴 강의를 참고하여 작성되었다. 강의는 Java로 진행되었으나, 이 포스트에서는 예제 코드를 C++로 작성하였다. 싱글톤 패턴의 개념을 명확하게 이해할 수 있도록 설명하고, C++ 코드를 통해 개념을 구체화하였다.


선수 지식

Singleton 패턴을 이해하기 위해 필요한 선수 지식은 다음과 같다.

  • 클클래스, 객체, static 키워드, 접근 제한자 등의 객체지향 프로그래밍 개념.
  • 특히, 전역 인스턴스의 관리와 객체 생성을 제어하는 메커니즘을 이해하는 것이 중요하다.

Singleton 패턴

싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 존재하도록 보장하는 디자인 패턴이다. 즉, 여러 곳에서 동일한 객체에 접근할 수 있게 하면서도, 객체가 단일 인스턴스만 생성되도록 제한하는 패턴이다.

Singleton 패턴의 역할

싱글톤 패턴의 주요 역할은 시스템 내에서 단일 인스턴스만 유지하며, 이 인스턴스에 대한 전역적인 접근을 제공하는 것이다. 일반적으로 자원을 많이 사용하거나 설정을 공유해야 하는 클래스에 적합하다. 예를 들어, 데이터베이스 연결 객체나 설정 파일을 읽는 객체에 싱글톤 패턴을 적용할 수 있다.

언제 Singleton 패턴을 사용해야 하는가?

  • 리소스 관리가 중요한 경우: 시스템 전체에서 하나의 리소스를 효율적으로 관리해야 할 때 싱글톤 패턴이 유용하다.
  • 글로벌 상태를 공유해야 하는 경우: 전역적인 설정이나 상태를 공유해야 하는 경우 싱글톤 패턴을 통해 효율적으로 관리할 수 있다.
  • 객체 생성을 제어해야 하는 경우: 인스턴스 생성에 있어 엄격한 제어가 필요하거나, 여러 객체가 동일한 상태를 유지해야 할 때 싱글톤 패턴이 적합하다고 한다.

Singleton 패턴의 구조

싱글톤 패턴의 구조는 다음과 같은 요소로 구성된다.

  1. Static Instance (정적 인스턴스): 싱글톤 클래스는 클래스 내에서 유일한 객체를 static 변수로 관리한다. 이 인스턴스는 클래스 외부에서 직접 접근할 수 없으며, 내부적으로만 관리된다.

  2. Private Constructor (비공개 생성자): 생성자를 private으로 선언하여, 외부에서 새로운 객체를 생성하지 못하도록 제한한다. 오직 클래스 내부에서만 객체를 생성할 수 있게 한다.

  3. Public Static Method (공개 정적 메소드): 인스턴스에 접근할 수 있는 유일한 방법은 public static를 사용하는 것이다. 객체가 이미 생성되었는지 확인하고, 없으면 새로 생성하며, 있으면 기존 객체를 반환한다.

게임으로 Singleton 이해하기

싱글톤 패턴은 게임 개발에서 매우 유용하다. 게임의 여러 시스템에서 전역적으로 관리해야 하는 데이터를 다룰 때, 하나의 객체를 공유하는 것이 필수적이기 때문이다. 다음은 싱글톤 패턴이 게임에서 어떻게 적용될 수 있는지에 대한 예시이다.

예시:

  1. GameManager (게임 상태 관리) 게임 내에서는 게임 상태를 전역적으로 관리해야 할 때가 많다. 예를 들어, 게임의 점수, 레벨, 적의 출현 상태 등을 관리하는 객체가 필요할 때, 이를 싱글톤으로 만들면 전역에서 일관된 상태를 관리할 수 있다. 하나의 GameManager 객체만 존재하면, 어느 위치에서도 이 객체를 참조해 게임의 상태를 쉽게 업데이트하거나 확인할 수 있다.

  2. AudioManager (오디오 관리) 게임에서 사운드는 매우 중요한 요소 중 하나이다. 배경음악이나 효과음 등 게임 전반에 걸쳐 동일한 사운드 시스템이 필요하다면, 이를 싱글톤 패턴으로 구현할 수 있다. 사운드 시스템이 전역적으로 관리되면 여러 씬이나 레벨에서도 일관된 오디오 환경을 제공할 수 있다.

  3. InputManager (입력 관리) 플레이어 입력을 처리하는 InputManager를 싱글톤으로 구현하면, 모든 게임 오브젝트가 동일한 입력 데이터를 공유할 수 있다. 이를 통해 각기 다른 오브젝트가 별도로 입력을 처리할 필요 없이 하나의 인스턴스에서 모든 입력을 통합 관리할 수 있게 된다.

여담으로 Unity에서는 입력 관리를 구현할 필요가 크게 없다. 기본적으로 제공하는 Input Manager를 이용하여 조건 처리를 하면 된다.

Singleton 패턴이 필요한 이유

  1. 일관된 상태 유지: 싱글톤 패턴을 사용하면 게임 내에서 일관된 상태를 유지할 수 있다. 예를 들어, 모든 씬(Scene)에서 동일한 GameManager 객체를 사용해 점수나 게임 상태를 관리할 수 있다. 이는 씬 전환이 일어나더라도 게임 데이터가 유지되도록 보장해 준다.

  2. 자원 절약 및 성능 최적화: 싱글톤은 불필요한 객체 생성을 방지해 메모리 절약과 성능 최적화를 가능하게 한다. 예를 들어, 여러 개의 AudioManager가 생성되면 동일한 사운드를 여러 번 로드해 메모리를 낭비할 수 있지만, 싱글톤으로 관리하면 한 번 로드된 자원을 재사용할 수 있다.

  3. 전역 접근 가능: 싱글톤 패턴은 전역적으로 객체에 접근할 수 있는 이점을 제공한다. 게임의 어느 곳에서나 GameManager의 인스턴스에 접근하여 게임 상태를 확인하고 업데이트할 수 있다. 이를 통해 게임의 여러 시스템이 동일한 상태를 공유할 수 있다.

  4. 씬 전환 시 유용성: 게임에서 씬(Scene)이 전환될 때마다 새로운 객체를 생성하는 것은 비효율적일 수 있다. 싱글톤 패턴은 씬 전환에도 동일한 인스턴스를 유지시킬 수 있으므로, 전역 상태를 유지하는 데 매우 유용하다.

예제 코드

img1

[main.cpp]


img1_1

[실행 결과]


한 눈에 코드를 보기 위해 선언부(.h)와 구현부(.cpp)로 분리하지 않고, main.cpp에서 통합하여 코드를 구현했다.

실제로 C++로 코딩할 때는 선언부와 구현부를 나눠서 코딩하는 것을 추천한다. 프로젝트가 커질 수록 선언부와 구현부로 나눠서 작업해야 유지 보수하기 편리하고, 효율적이기 때문이다.

코드 설명

1.ScoreManager 클래스(싱글톤 클래스)

img2

[ScoreManager 클래스(싱글톤 클래스)]


  • 싱글톤 패턴: ScoreManager 클래스는 게임에서 점수를 전역적으로 관리하기 위해 싱글톤 패턴을 사용한다. 이 패턴을 통해 단 하나의 인스턴스만 생성되며, 모든 게임 캐릭터가 동일한 인스턴스를 공유한다.
  • static 포인터: ScoreManager는 정적 포인터 변수 instance를 사용하여 인스턴스를 저장한다. 이를 통해 여러 객체가 동일한 점수 관리 객체를 사용할 수 있다.
  • private 생성자: 생성자를 private으로 선언하여, 외부에서 이 클래스를 통해 직접 객체를 생성하는 것을 방지한다. 오직 getInstance() 메소드를 통해서만 인스턴스를 생성하거나 가져올 수 있다.
  • getInstance(): 이 메소드는 ScoreManager 객체를 반환한다. 만약 객체가 아직 생성되지 않았다면 새로 생성하고, 이미 생성된 경우 기존 객체를 반환한다.
  • 점수 관리 메소드: AddScore() 메소드는 캐릭터가 점수를 획득할 때 호출되고, ShowScore() 메소드는 현재 점수를 출력한다.

2.Character 클래스(게임 캐릭터)

img3

[Character 클래스(게임 캐릭터)]


  • Character 클래스는 게임 캐릭터를 나타낸다. 캐릭터는 점수를 획득할 수 있으며, 이때 점수는 ScoreManager를 통해 관리된다.
  • EarnPoints(): 캐릭터가 점수를 획득하면, 점수는 ScoreManager::getInstance()를 통해 싱글톤 객체에 전달되어 누적된다. 이를 통해 여러 캐릭터가 점수를 얻어도 공유된 점수 객체에서 일관되게 관리된다.

3. 인스턴스 초기화

img4

[인스턴스 초기화]


  • 정적 멤버 변수 초기화: ScoreManager 클래스의 정적 멤버 변수 instance는 클래스 외부에서 초기화해야 한다. 이 코드를 통해 instance는 프로그램 시작 시 nullptr로 초기화되고, getInstance() 메소드가 처음 호출될 때 인스턴스가 생성된다. 이를 통해 싱글톤 패턴에서 인스턴스가 오직 한 번만 생성됨을 보장할 수 있다.

4. 클라이언트(main함수)

img5

[클라이언트(main함수)]


  • player1과 player2: 두 개의 캐릭터 객체가 생성된다.
  • 점수 획득: player1과 player2는 각각 50점과 100점을 획득한다. 이 점수는 모두 ScoreManager의 단일 인스턴스에서 관리된다.
  • 점수 출력: ShowScore() 메소드를 호출하여 누적된 점수를 출력한다. 두 캐릭터의 점수는 싱글톤 객체에서 일관되게 관리되기 때문에 모든 캐릭터의 총 점수가 출력된다.

Q&A

Q1. 싱글톤 패턴에서 왜 정적 멤버 변수를 사용하는가?

  • A1: 정적 멤버 변수는 클래스 수준에서 하나만 존재하며, 모든 객체가 이 변수를 공유한다. 싱글톤 패턴에서는 인스턴스를 오직 한 번만 생성하고 이를 모든 클라이언트가 공유해야 하므로 정적 멤버 변수를 사용해 인스턴스를 저장해야 한다. 그렇기에 언제든지 동일한 인스턴스에 접근할 수 있다.

Q2. 왜 싱글톤 패턴에서 생성자를 private으로 설정하는가?

  • A2: 생성자를 private으로 설정하면 클래스 외부에서 인스턴스를 생성할 수 없다. 싱글톤 패턴에서 인스턴스가 오직 하나만 생성되는 것을 보장하기 위해 필요하다. 외부에서 생성자를 호출해 추가 인스턴스를 만들 수 없도록 막아야 하기 때문에 생성자를 private으로 설정해야 한다.

Q3. 싱글톤 패턴에서 nullptr로 초기화하는 이유는 무엇인가?

  • A3: nullptr로 초기화하는 이유는 인스턴스가 아직 생성되지 않았음을 명확히 나타내기 위함이다. 이후에 getInstance() 메소드가 처음 호출될 때 인스턴스를 생성하고, 그 후에는 항상 동일한 인스턴스를 반환하도록 해야 한다. 이렇게 함으로써 프로그램 실행 중에 인스턴스가 단 한 번만 생성되도록 보장할 수 있다.

Q4. 싱글톤 패턴을 사용할 때 메모리 누수는 발생하지 않는가?

  • A4: 메모리 누수가 발생할 수 있다. 싱글톤 패턴에서 동적으로 할당된 인스턴스는 프로그램이 종료될 때까지 유지된다. 따라서 프로그램 종료 시 적절한 메모리 해제가 이루어지지 않으면 메모리 누수가 발생할 수 있다. 이를 방지하기 위해 싱글톤 패턴에서 소멸자를 잘 관리하거나, std::unique_ptr 같은 스마트 포인터를 사용해 자동 메모리 관리를 적용할 수 있다고 한다.

Q5. 싱글톤 패턴은 언제 사용하면 좋은가?

  • A5: 싱글톤 패턴은 전역적으로 하나의 인스턴스만 필요할 때 유용하다. 예를 들어, 게임의 점수 관리 시스템(ScoreManager), 로그 시스템, 설정 관리 시스템 등에서 싱글톤 패턴을 사용할 수 있다. 이러한 시스템은 여러 곳에서 접근되지만, 하나의 일관된 인스턴스만 있어야 하는 경우에 적합하다.

Q6. 싱글톤 패턴의 단점은 무엇인가?

  • A6: 싱글톤 패턴의 단점은 의존성이 커질 수 있다는 점이다. 프로그램 전체에서 단 하나의 인스턴스를 공유하기 때문에 이 인스턴스에 대한 의존도가 높아질 수 있고, 테스트가 어려워질 수 있다. 그리고 멀티스레딩 환경에서는 동시 접근 문제가 발생할 수 있어, 추가적인 동기화 처리가 필요하다.

결론

싱글톤(Singleton) 패턴은 프로그램 내에서 오직 하나의 인스턴스만 존재하도록 보장하는 객체지향 디자인 패턴이다. 주로 전역적으로 하나의 인스턴스가 필요하고, 이를 여러 곳에서 접근해야 하는 상황에서 유용하게 사용된다. 특히 게임과 같은 시스템에서는 점수 관리, 설정 관리, 자원 관리 등의 기능에 싱글톤 패턴을 적용하여 일관된 데이터를 유지하고 관리할 수 있다.

싱글톤 패턴의 장점은 전역 인스턴스의 일관성을 보장하면서도 객체 생성의 제어를 단순화할 수 있다는 것이다. 이는 전역 변수를 사용하지 않고도 프로그램의 특정 부분에서 공통으로 사용하는 데이터를 공유하고 관리할 수 있는 장점을 제공한다.

하지만 싱글톤 패턴은 멀티스레드 환경에서 주의가 필요하며, 의존성이 높아질 수 있는 단점도 있다. 특히, 너무 빈번하게 사용되면 코드의 테스트 가능성과 유지보수성이 떨어질 수 있기 때문에 적절한 상황에서 신중하게 사용해야 한다.

결론적으로 싱글톤 패턴은 효율적이고 일관된 자원 관리가 필요한 프로그램에서 매우 유용한 패턴이며, 특히 게임 개발과 같은 특정 도메인에서 자주 활용되는 중요한 디자인 패턴 중 하나이다. 이를 통해 코드의 복잡성을 줄이고, 시스템의 안정성을 높일 수 있다.

함께 보면 좋은 자료

함께 보면 좋은 자료는 다음과 같다.

참고 자료

본 포스트를 작성할 때 참고한 자료들이다.

This post is licensed under CC BY 4.0 by the author.