본문 바로가기

Unity/디자인 패턴

싱글톤 패턴(Singleton Pattern)

싱글톤 패턴을 따르는 클래스는 생성자가 여러 차례 호출되더라도 객체는 하나이고 최초 생성 이후 호출된 생성자는 최초의 생성자가 생성한 객체를 return 합니다. 이와 같은 디자인 유형을 싱글톤 패턴이라고 합니다.

 

해당 객체를 싱글톤으로 구현하여 모든 유저 또는 프로그램들이 해당 객체를 공유하며, 사용하도록 할 때 사용됩니다.

간단하게 쉽게 말하자면 메모리 절약을 위해, 인스턴스가 필요로 할 때 똑같은 인스턴스를 새로 만들지 않고, 기존의 인스턴스를 가져와 활용하는 기법을 말합니다.

 

우리가 전역 변수를 만들어 이용하는 이유는, 똑같은 데이터를 메서드마다 지역 변수로 선언해서 사용하면 무의미하지 않고, 낭비이기 때문에 전역에서 한번만 데이터를 선언하고 가져와 사용하면 효율적이기 때문입니다. 이러한 개념을 그대로 클래스에 대입한 것이 싱글톤 패턴이라고 이해하면 쉽습니다.

따라서, 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 그 객체가 리소스를 많이 차지하는 역할을 하는 무거운 클래스일 때 적합합니다.

 

이처럼, 싱글톤 클래스는 고정된 메모리 영역을 가지고 하나의 인스턴스만 사용하기 때문에 메모리 낭비를 방지할 수 있으며, 주로 공통된 객체를 여러 개 생성해서 사용하는 *DBCP(Database Connection Pool)와 같은 상황에서 많이 사용됩니다.

 

*DBCP(Database Connection Pool) - 클라이언트와 서버 사이드인 웹 어플리케이션에서, 사용자의 요청에 따라 Connection이 생성된다면 수 많은 사용자가 요청을 했을 때 서버에 과부하가 걸리게 됩니다.

이러한 상황을 예방하기 위해 미리 일정 개수의 Connection을 만들어 Pool에 저장을 하고, 사용자의 요청이 발생하면 Connection을 제공하고 사용자와의 연결이 종료된다면 Pool에 다시 반환하여 보관하는 것을 의미합니다.

 

즉, 싱글톤패턴은 아래와 같은 상황에 사용을 합니다.

- 프로그램 내에서 하나의 객체만 존재해야 한다.

- 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용해야 한다.

 

싱글톤 패턴을 사용하는 이유

1. 메모리 측면의 이점

싱글톤 패턴을 사용하게 된다면 한 개의 인스턴스만을 고정 메모리 영역에 생성하고, 추루 해당 객체를 접근할 때 메모리 낭비를 방지할 수 있습니다.

2. 속도 측면의 이점

생성된 인스턴스를 사용할 때는 이미 생성된 인스턴스를 활용하여 속도 측면에 이점이 있습니다.

3. 데이터 공유가 쉽다.

전역으로 사용하는 인스턴스이기 때문에 다른 여러 클래스에서 데이터를 공유하며 사용할 수 있습니다.

하지만, 동시성 문제가 발생할 수 있어 이 점은 유의하며 설계해야 합니다.

 

싱글톤의 문제점

1. 모듈간 의존성이 높아집니다.

대부분의 싱글톤을 이용하는 경우 인터페이스가 아닌 클래스의 객체를 미리 생성하고 정적 메서드를 이용해 사용하기 때문에 클래스 사이에 강한 의존성과 높은 결합이 생기게 됩니다.

즉, 하나의 싱글톤 클래스를 여러 모듈들이 공유를 하니까, 만일 싱글톤의 인스턴스가 변경되면 이를 참조하는 모듈들도 수정이 필요하게 됩니다.

또한, 클라이언트 코드에서 너무 많은 곳에서 사용하면 클래스간의 결합도가 높아져 오히려 패턴을 사용 안하느니만 못하게 될 수도 있습니다.

 

2. S.O.I.L.D 원칙에 위배되는 사례가 많습니다.

우선, 싱글톤 인스턴스 자체가 하나만 생성하기 때문에 여러 가지 책임을 지니게 되는 경우가 많아 딘일 책임 원칙(SRP)를 위반하기도 하고, 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되어 개방-폐쇄 원칙(OCP)에도 위배됩니다.

그리고, 의존 관계상 클라이언트가 인터페이스와 같은 추상화가 아닌 구체 클래스에 의존하게 되어 의존 역전 원칙(DIP)도 위반하게 됩니다.

따라서, 싱글톤 인스턴스를 너무 많은 곳에서 사용할 경우 잘못된 디자인 형태가 될 수 도 있습니다.

그래서 싱글톤 패턴을 객체 지향 프로그래밍의 안티 패턴이라고 불리기도 합니다.

 

3. TDD 단위 테스트에 애로사항이 있습니다.

마지막으로 싱글톤 클래스를 사용하느 모듈을 테스트하기 어렵다는 것입니다.

단위 테스트를 할 때, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행 할 수 있어야 하는데,

싱글톤 인스턴스는 자원을 공유하고 있기 때문에, 테스트가 결함 없이 수행되려면 매 번 인스턴스의 상태를 초기화시켜줘야 합니다. 그렇지 않으면 어플리케이션 전역에서 상태를 공유하기 때무네 테스트가 온전하게 수행되지 못할 수 도 있습니다. 많은 테스트 프레임워크 Mock 객체를 생성할 때 상속에 의존하기 때문에 싱글톤의 클라이언트 코드를 테스트하기 어렵습니다.

 

이처럼 싱글톤 기법은 오직 한 개의 인스턴스 생성을 보증하여 효율을 찾을 수 있지만, 그에 못지 않게 수반되는 문제점도 많습니다. 결과적으로 이러한 문제들을 안고 있는 싱글톤 패턴은 유연성이 많이 떨어지는 패턴이라고 할 수 있습니다.

 

그래서 직접 유저가 만들어 사용하는 것보다는, 스프링 컨테이너 같은 프레임워크의 도움을 받으면 싱글톤 패턴의 문제점들을 보완하면서 장점의 혜택을 누릴 수 있습니다.

 

스프링 프레임워크에서는 싱글톤 패턴이란 것이 없고, 내부적으로 클래스의 제어를 IoC(Inversion of Control) 방식의 컨테이너에게 넘겨 컨테이너가 관리하기 때문에, 이를 통해 평범한 객체도 하나의 인스턴스 뿐인 싱글톤으로 존재가 가능하기 때문에 싱글톤 단점이 없습니다.

 

따라서 만일 프레임워크 도움 없이 싱글톤 패턴을 적용하고 싶다면, 위에서 살펴 본 장·단점을 잘 고려하여 사용하는 것이 좋습니다!

 

유니티에서 사용한 싱글톤 패턴입니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
            	// 데이터 타입을 찾는다.
                _instance = (T)FindObjectOfType(typeof(T));

		// 만약 찾았는데도 존재하지 않다면
                if (_instance == null)
                {
                // 새로운 게임 오브젝트를 만들어서 적용
                    GameObject newObj = new GameObject(typeof(T).Name, typeof(T));
                    _instance = newObj.GetComponent<T>();
                }
            }

            return _instance;
        }
    }

    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            
            // 현재 transform이 부모를 상속받고 있는지 체크
            if (transform.root != null && transform.parent != null)
            {
            	// 만약 상속받고 있다면, 부모의 오브젝트까지 DontDestroy
                DontDestroyOnLoad(this.transform.root.gameObject);
            }
            else
            {
                DontDestroyOnLoad(this.gameObject);
            }
        }
        else
        {
            Destroy(this.gameObject, Time.deltaTime);
        }
    }
}

 

'Unity > 디자인 패턴' 카테고리의 다른 글

FSM (Finite-State Machine)  (0) 2024.07.03