궁극의 Unity 싱글톤 패턴 #4 - 마무리

이오싶·2024년 11월 8일

우리의 여정이 마무리에 다다랐습니다. 지금까지 배운 대로 Lazy InitializationGeneric화를 적용한 DontDestroyOnLoad 싱글톤 스크립트는 다음과 같습니다.

public class UniversalSingleton<T>
{
	private static string objectName = "@Managers"
	private static T instance = null;
    public static T Instance 
    {
        if (instance == null)
        {
            InitSingleton();
        }
        return instance;
    }
    
    private static void InitSingleton() 
    {
        if (instance != null) return;
        instance = FindObjectOfType<T>();
        if (instance == null)
        {
        	var manager = GameObject.Find(objectName);
        	if (manager == null)
        	{
            	manager = new GameObject
            	{
                	name = objectName
            	};
        	}
        	instance = manager.AddComponent<T>();
        }
        instance.Init();
        DontDestroyOnLoad(manager);
    }
    
    private void Awake()
    {
    	if (Instance != this)
        {
            if (Instance.gameObject != gameObject) Destroy(gameObject);
            else Destroy(this);
            return;
        }
    }
    
    protected abstract void Init();
}![](https://velog.velcdn.com/images/gpotatok/post/55db1559-0c74-4470-8917-0fc6ad6d3e9d/image.png)

멋있는 코드입니다. 지금 당장 이 코드를 복사하셔서 여러분의 프로젝트에 적용해도 수정할 점이 거의 없을 거라고 생각합니다. 단, 사용 시 몇 가지 주의할 점이 있습니다.

Awake를 자손 클래스에서 재정의하지 말것.

사실 virtual 또는 abstract로 정의되지 않은 부모 클래스의 메소드를 파생 클래스에서 재정의하지 않아야 한다는 것은 OOP의 기본입니다. 저는 이게 문법 단위로 강제되어야 한다고 봅니다.

코드에서 Awake는 자신의 싱글톤 인스턴스 여부를 확인하기 위해서만 사용하고 있으며, Lazy Initialization을 적용한 싱글톤 인스턴스의 초기화 시점은 Awake가 호출되는 시점이 아닐수도 있기 때문에 이 메소드를 함부로 덮어쓰거나 초기화 로직을 추가하게 되면 목표한 대로 동작할 수 없게 됩니다.

우리는 이미 파생 싱글톤 클래스의 초기화를 위해 Init을 추상 메소드로 제공하고 있습니다.

애초부터 싱글톤은 장단점이 명확한 패턴.

디자인 패턴을 주제로 하는 많은 문서에서 싱글톤이 언급되면 일단 싱글톤을 까거나, 아니면 싱글톤을 그래도 변호하기 위해 싱글톤을 까는 내용을 인용하며 시작합니다. 그만큼 싱글톤 패턴은 단점이 명확한 패턴이며, 어떤 점에선 많은 디자인 패턴이 목표로 하는 SOLID 원칙을 역행하는 모습을 보이기도 합니다.

이는 싱글톤 패턴이 별도의 레퍼런스 형성 없이 전역적인 접근을 가능하게 한다는 점에서 비롯되는 문제입니다. 싱글톤의 이런 특징은 마치 정적 변수 및 클래스의 특징과도 일치해서, 소수의 데이터에 대해 여러 객체에서 잦은 접근을 요구하는 경우 편리함을 제공해주지만, 한 편으론 명시되지 않은 종속성을 쉽게 형성하게 됨에 따라 클래스 간 결합도(커플링)를 상승시키게 됩니다.

이로 인해 싱글톤 패턴은 모듈화 설계와 각 모듈의 독립적 테스트에 상당 경우 걸림돌이 됩니다. 당신의 캐릭터를 다른 씬에서 테스트해보려 했더니 싱글톤 매니저가 부재하는 바람에 망가지는 경험... 한번씩 해보셨을거라 생각합니다.

다만 Lazy Initialization이 이 문제를 해결하는 데에 의도치 않게 도움을 주기도 합니다... 의존성이 형성되면 어떤 신이든 인스턴스를 알아서 생성해서 가져오기 때문이죠. 물론 데이터 초기화가 씬 의존적(인스펙터 등)이라면 말짱 도루묵이지만요.

그렇담 어떻게 해야 좋은가?

물론 싱글톤은 코어 패턴이 직관적이고, 압도적인 편의성이라는 장점을 가지고 있는 만큼 소~중규모 프로젝트에서 적절히 사용하면 생산성을 매우 증가시킬 수 있습니다. 단, 너무 남용해서는 안됩니다.

  • 싱글톤을 사용하기로 결정했으면 최소한으로 유지하고,

  • 전역 접근의 장점을 활용할 수 있는 소수의 스크립트에만 사용하며,

  • 싱글톤 인스턴스에 대한 접근 시 결합도에 대한 염두를 항상 두어야 합니다.

  • 독립적 테스트를 위해, 싱글톤 인스턴스와 결합을 형성한 컴포넌트에서 참조하는 데이터에 대한 널 체크 및 예외처리를 습관화하는것도 필요하겠습니다.

이것으로 궁극의 Unity 싱글톤 패턴 시리즈는 마무리하도록 하겠습니다. 포스트를 비몽사몽하면서 쓰는 바람에 여러 오류가 있을 수 있으니, 친절하고 활발한 피드백 및 제보는 대환영합니다.

profile
Iosif2510

0개의 댓글