오늘 한 일
- 챌린지반 과제 풀이 (제너릭 싱글턴 구현하기)
- 팀 프로젝트 진행 (오브젝트 풀링, 파워업 추가)
- 챌린지반 특강 듣기 (Delegate, event, UnityAction)
- 여태까지 배운 내용 시험 풀기
오늘은 선언해두면 언제 어디서든 접근이 가능한 싱글턴 패턴에 대해서 알아보자
싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근 지점을 제공하는 생성 디자인 패턴입니다. 리팩토링 구루
- 모든 인스턴스들이 싱글턴 패턴에게 접근이 가능하다.
싱글턴을 선언할 때 가장 중요한 키워드에 대해서 설명해주겠다.
- 인스턴스가 아닌 클래스에 속하는 정적 멤버를 선언할 때 사용하는 키워드입니다.
- 클래스, 인터페이스 및 구조체에서 필드, 메서드, 속성, 연산자, 이벤트 및 생성자에 static 키워드를 추가할 수 있습니다
- static 한정자는 인덱서 또는 종료자와 함께 사용할 수 없습니다.
public class Class1 { public static int staticField = 2; public static void staticMethod() { Console.WriteLine("저는 정적 메서드입니다. 바로 외부에서 호출이 가능합니다."); } } // 호출 해보기 static void Main(string[] args) { Console.WriteLine(Class1.staticField); Class1.staticMethod(); }
- 정적 선언한 필드와 메서드들은 인스턴스 선언 없이 바로 외부로 호출이 가능하다.
% static 사용 시 주의사항 %
1. static이 아닌 멤버에서는 static 멤버를 사용 가능하지만 static인 멤버에서 static이 아닌 멤버를 사용할 수 없다
(사용하려면 인스턴스 생성을 해줘야한다)
- 그래서 위와 동일한 이유로 Progam 클래스에 선언했다고 해서 Main 함수에서 사용이 불가능한 이유와 동일하다.
2. static 멤버는 인스턴스 단위로 참조가 불가능하다.
- static은 아예 클래스 소속이기 때문에 클래스 자체로 불러줘야한다.
static은 아예 클래스 자체에 속하기 때문에 이 점을 이용해서 싱글턴을 만들어내는 것이다.
유니티에서 가장 기초적인 싱글턴
public class GameManager : MonoBehaviour { public static GameManager instance; void Awake() { instance = this; // this는 클래스의 현재 인스턴스를 가리킨다. } }
VisualStudio에서 기초적인 싱글턴
public class GameManager { public static GameManager instance = new GameManager(); // Awake 같은 건 없으니 이로 대체해주자. public int field = 1; } // 사용해보기 static void Main(string[] args) { GameManager.instance.field = 4; Console.WriteLine(GameManager.instance.field); }
- 숫자가 잘 변경되고 호출된 모습을 볼 수 있다.
- 해당 코드들을 잘 보면 static을 이용해 클래스 소속 클래스타입 멤버를 선언해서 거기에 자기 자신을 할당하는 방식이다.
이것도 싱글턴 패턴인가요?
static class staticClass { public static int staticField = 4; public static void staticMethod() { Console.WriteLine("정적 클래스의 정적 메서드입니다."); } }
- 물론 싱글톤과 비슷하게 static 필드를 바꿀 수 있고 메서드도 호출 가능하지만, 싱글턴에 비해서 제한적인 사항이 많다.
- 정적 멤버만 선언 가능
- 인터페이스 구현 불능
- 상속과 재정의 불능
- 정적 클래스는 단순히 서로 연관된 메서드를 묶어두는 것에만 가깝기 때문에 절차적 프로그래밍에 가깝다고 본다.
- 해당 코드의 사용처는 주로 유틸리티 클래스, 수학 함수 등에서 사용된다.
싱글턴의 편의성 때문에 사용에 주의해야 한다.
1. 싱글턴 클래스가 많은 책임과 기능 짊어질 수도 있다.
- 가능하다면 최대한 여러 클래스로 분담해주는 것이 좋다.
(의존도와 결합도에 유의해줘야한다.)2. 객체 생성 시기를 제어할 수 없다.
- 그래서 주로 항상 상주해야만 하는 GameManager나 AudioManager과 같은 곳에 싱글톤을 사용하는 편이다.
3. 유니티에서는 겹쳐서 생성되는 경우를 주의해야한다.
- if (instance == null)을 통해 클래스의 instance가 겹치지 않게 유의해줘야한다.
현재 사용하는 싱글턴
public class SystemManager : MonoBehaviour { public static SystemManager instance; void Awake() { if (instance == null) { instance = this; DontDestroyOnLoad(instance); // 씬이 넘어가도 파괴되지 않게하기 } else { Destroy(gameObject); // 겹치는 건 파괴 }
제너릭 싱글턴(오브젝트 탐색)
코드 출처 - 실리의 프로그램 사이트
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { private static T instance; public static T Instance { get { if (instance == null) { GameObject obj; obj = GameObject.Find(typeof(T).Name); if (obj == null) { obj = new GameObject(typeof(T).Name); instance = obj.AddComponent<T>(); } else { instance = obj.GetComponent<T>(); } } return instance; } } public void Awake() { DontDestroyOnLoad(gameObject); } }
- 해당 제너릭 싱글턴은 오브젝트를 기준으로 찾아내기 때문에 해당 싱글턴을 상속받은 클래스와 오브젝트의 이름이 동일해야한다.
리팩토링 구루 - 싱글톤 패턴
Learn.Microsoft - static 한정자
실리의 프로그램 사이트 - Unity Singleton을 제네릭으로 만들어놓고 써봅시다
기존에 알고 있음에도 불구하고 다시 써보고 정리해보니 전보다 이해가 깊이 되고 좀 더 발전된 방식의 싱글톤 사용이 가능해졌다. 기존 지식을 회고하는 것만으로도 코드 기획에 많은 도움이 되는 것 같다.