디자인 패턴 중 우리가 제일 쉽게 접근하는 패턴은 바로 "싱글톤 패턴 " 이다. 오늘은 많이 사용하는 싱글톤 패턴에 자세히 알고 앞으로 제대로 사용하고 싶어서 관련 내용을 작성해 보고자 한다.
🔷 싱글톤 패턴이란?
추상 객체 인스턴스 생성 패턴 중 하나로 다른 패턴과 다르게 싱글톤 패턴은 정말 단순하다. 객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다. 여러 개의 스레드가 공유해야 할 정보들을 하나의 인스턴스에 담아 놓고, 싱글턴 패턴으로 설계하면 모든 스레드에 공통적으로 적용할 수 있다.
싱글톤을 구현하는 방법은 여러 가지 있지만 유니티에서 주로 사용하는 코드 구조를 아래와 같이 작성할 수 있다.
public class Singleton : MonoBehaviour
{
public static Singleton Instance;
private void Awake()
{
if(Instance != null)
Destroy(gameObject); //Instance가 이미 존재한다면 삭제
else
{
Instance = this; //나 자신은 Instance에 넣어줌
DontDestroyOnLoad(this.gameObject); //OnLoad 될때 자신을 파괴하지않고 유지
}
}
}
🔷 싱글톤 장점
위의 이미지는 싱글톤 화한 클래스가 Heap 메모리에 할당이 되고 다른 클래스에서 쉽게 접근한다는 것을 표현해 보았다.
🔸 메모리 측면
최초 한 번의 new 연산자를 통해서 고정된 메모리 영역을 사용하여 중복생성과 해당 객체에 접근할 때 메모리 낭비는 방지한다. 또한 생성된 인스턴스를 활용하여 속도 측면에서 이점을 볼 수 있다.
🔸 데이터 공유가 쉬움
싱글톤 인스턴스의 경우 전역으로 사용되기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있다.
But ❗ 여러 클래스의 인스턴스에서 싱글톤 인스턴스의 데이터에 접근하게 되면 동시성 문제가 발생할 수 있다.
🔷 싱글톤 단점
🔸 과할 경우 성능이 낮아진다
전역(static)으로 정적 메모리에 할당된 객체이기 때문에 큰 메모리가 쌓일 경우 프로그램의 성능이 낮아진다.
정적 메모리를 사용하기 때문에 병렬처리나 동기화 같은 메모리에 접근할 때 문제가 생길 수 있습니다.
🔸 결합도가 높아짐
너무 많은 데이터를 공유할 경우, 싱글톤 인스턴스와 다른 클래스 간의 결합도가 높아져 개방 폐쇄 원칙에 위배될 수 있다.
💡 개방 폐쇄 원칙(OCP) - Open Closed Principle
▪기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계되어야한다는 원칙
▪확장에 대해서는 개방적 (Open), 수정에 대해서는 폐쇄적(Closed)으로 정의된다.
유니티의 사용 예시
Scene에서 Scene을 넘어갈 때 지키기고 싶은 데이터가 있을때가 있다.
하지만 아래의 이미지와 같이 Scene이 넘어가는 경우 Level의 값이 유지되는게 아닌 초기화 되는 것을 확인 할 수 있습니다.
Scene이 넘어가도 데이터를 유지하는 방법은 무엇일까?
이때 싱글톤을 이용하여 GameManager 스크립트를 제작하고 데이터를 유지할 수 있습니다.
GameManager의 스크립트를 아래와 같이 짜준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public int level = 0; //유지하고 싶은 level 데이터
private void Awake()
{
if (Instance != null)
Destroy(this.gameObject);
else
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
}
1. 빈 게임오브젝트(Empty Object)를 생성한 뒤 "GameManager"
2. 해당 오브젝트에 작성한 GameManager 를 추가
이러면 GameManager를 사용하기 위한 준비 끝!
어디에서는 아래와 같이 작성을 하면 GamaManager의 데이터에 접근 할 수 있습니다.
GameManager.Instance.(변수명 or 함수명)
각 Scene의 버튼에 아래의 OnClick를 연결하면 테스트를 진행을 하게된다면 아래와 같이 확인이 가능합니다.
public void OnClickLevelUp() //레벨 업 버튼
{
GameManager.Instance.level++;
}
public void OnClickLevelDown() //레벨다운 버튼
{
GameManager.Instance.level--;
}
Scene이 변하더라도 level의 값이 변경되지않는 것을 확인할 수있습니다!
이때 DontDestroyOnLoad(gameObject)을 통해 해당 오브젝트를 파괴되지않고 유지되는 것을 확인 할 수 있다.
유니티에서의 싱글톤 역할
◾ 단일 시스템 자원 관리 차원
◾ 게임 시스템 상 전역 변수의 역할
◾ 씬 로드 시 데이터가 파괴가 되지 않고 유지
◾ 여러 오브젝트가 접근
◾ 단 한 개의 객체만 존재
❗ 상태를 가진 객체를 싱글톤을 만들면 안된다. 그 이유는 각자 다른 스레드에서 객체의 상태를 변경시킬 여지가 있기 때문 !
결론
오직 한 개의 인스턴스 생성을 보증하여 효율을 찾을 수 있지만 그거에 동반되는 문제점도 많아 싱글턴이 정말 필요할 때 사용해야 한다는 것을 알게 되었다. 처음에는 데이터 접근하기 쉽게 다 싱글턴화 시키는 게 편하지 않을까?라는 생각을 했었는데 오히려 접근하기 쉬워 비대칭적인 크기로 게임이 만들어질 수 있다는 것을 알게 되었다.
정말 필요할 때만 사용하는 것이 좋을 것 같다..!
그럼 GameManager에는 어떤 데이터를 담고있는 것이 좋을까? 한번 공부를 하고 정리를 해봐야겠다.
참고 사이트