들어가며
본 서적의 화자는 ‘싱글턴 패턴은 득보다는 실이 많다.’ 라고 언급하며
다른 패턴들과는 별개로 어떻게 하면 싱글턴 패턴을 지양하는가를 서술한다.
싱글턴 패턴에 대하여
- 오직 한개의 클래스 인스턴스만 갖도록 보장
- 인스턴스가 여러 개라면?
= 외부 시스템과 상호작용하며 전역상태 관리하는 클래스
- ex) 파일 시스템 API 래핑 시스템
- 파일 작업 완료 시간을 고려하여 클래스는 비동기로 동작
= 여러 작업이 동시에 진행될 수 있음.
- 한쪽에선 파일 생성, 한쪽에선 방금 생성한 파일 삭제 등등
- 파일 작업 완료 시간을 고려하여 클래스는 비동기로 동작
- 래퍼 클래스가 두 작업을 파악해서 조율해야함.
- ex) 파일 시스템 API 래핑 시스템
→ 클래스로 들어온 호출이 이전 작업 전체에 접근할 수 있어야함.
- 아무데서나 인스턴스를 만들 수 있다면 다른 인스턴스의 작업에 접근 불가.
→ 싱글턴으로 만들면 클래스가 인스턴스를 하나만 가지므로 컴파일 단계에서 강제
- 전역 접근점을 제공
- 호출하고자 하는 시스템에서 파일 시스템 클래스 인스턴스를 생성할 수 없다면?
=하나의 인스턴스 생성 + 전역 접근
- 호출하고자 하는 시스템에서 파일 시스템 클래스 인스턴스를 생성할 수 없다면?
예제 코드
일단 생략
ㄹ
ㄹ
ㄹ
ㄹ
왜 사용하는가
- 장점들
- 한 번도 사용하지 않는다면 아예 인스턴스를 생성하지 않는다.
- 처음 사용될 때 초기화되므로
- 이건 유니티에서도 동일한가?
- 가령 오브젝트로 꺼내놓은 사운드 매니저 등은?
- 런타임에 초기화된다.
- 보통 싱글턴 대안으로 정적 멤버 변수를 많이 사용한다.
- 그러나 컴파일러는 main 함수를 호출하기 전에 정적 변수를 초기화 하기에 프로그램 실행 뒤에 알게되는 정보를 활용할 수 없다.
- 싱글턴은 최대한 늦게 초기화됨( : 게으른 초기화 )
- 싱글턴을 상속할 수 있다 (싱글턴 오버라이드)
- ex) 특정 시스템 클래스가 Switch, Ps5, Steam등의 크로스 플랫폼을 지원한다 가정
- 파일 시스템 래퍼로 추상 인터페이스를 만든 뒤, 플랫폼마다 구체 클래스를 만들면 됨.
코드
private enum Platform { PS5, Switch } public class FileSystem : MonoBehaviour { public static FileSystem instance; private Platform _platform; void Awake() { if(_platform == PS5) instance = new PS5FileSystem(); else if(_platform == Switch) instance = new SwitchFileSystem() else instance = this; } virtual FileSystem() {} virtual string readFile(string path) {} virtual void writeFile(string path, string contents) {}; }
→상위 클래스
→추가로 포인터변수(char*)를 C#에서 대체하기 어렵기에 string으로 뭉개버려서 문제가 발생할 지도
public class PS5FileSystem : FileSystem { override string readFile(string path) { //소니의 파일 IO API 사용 } override void writeFile(string path, string contents) { //소니의 파일 IO API 사용 } } ~~~
→하위 클래스
- 한 번도 사용하지 않는다면 아예 인스턴스를 생성하지 않는다.
무엇이 문제인가
- 알고보니 전역변수
- 코드 이해하기 힘들어
- 커플링 조장
- 동시성 프로그래밍에 알맞지 않다.(멀티스레딩 등)
- 문제가 하나 뿐일 때에도 두 가지 문제를 풀려 든다.
- 게으른 초기화는 제어할 수가 없다.
대안책
- 클래스가 꼭 필요한가?
- 오직 한 개의 클래스 인스턴스만 갖도록 보장하기
- 인스턴스에 쉽게 접근하기
- 넘겨주기
- 상위 클래스로부터 얻기
- 전역인 객체로부터 얻기
- 서비스 중개자로부터 얻기
정말로 싱글턴을 사용해야하는 시점
Uploaded by N2T