[Design Patterns] 디자인 패턴

내발은개발내손은개발·2026년 1월 21일

싱글턴 패턴(Singleton Pattern)

: 클래스를 하나의 인스턴스로만 존재하도록 만드는 패턴

  • 하나만 존재(유일성)할 수 있는 클래스 -> 다른 클래스와 혼동할 가능성 적음, 편리하게 접근 가능
  • 원하는 시점에 생성 -> 유연한 초기화
  • MonoBehaviour를 상속받을 수 있다 -> 다양한 C# 기능활용
  • 객체로써 존재 가능 -> 객체 지향의 디자인 패턴
  • 단 하나의 인스턴스(객체)만 생성되도록 강제하고, 어디서든 그 객체에 쉽게 접근할 수 있게 만든 패턴
  • 게임 전체를 관장하는 시스템에 주로 사용
  • (주로 관리자(Manager)역할을 하는 스크립트에 사용)
단순한 싱글톤
public class SimpleSingleton : MonoBehaviour
{
	public static SimpleSignleton Instance {get; private set;}
	-> static 기반의 Instance    
    void Awake()
    {
    	Instance = this;
        -> 필요할 때 Instance에 this 할당
    }
}
-> 유일성이 보장된거는 아닌 단순한 static 기반의 싱글톤 생성
일반적인 싱글톤
public class BasicSingleton : MonoBehaviour
{
	private static BasicSingleton instance;
    public static BasicSingleton Instance
    {
    	get => instance;
    }
    
    void Awake()
    {
    	if(instance == null)
        {
        	instance = this; -> 싱글턴 인스턴스 할당
            DontDestoryOnLoad(gameObject); -> 씬 전환되도 파괴되지 않도록 설정
        }
        else
        {
        	Destroy(gameObject); -> 중복 방지
        }
    }
}
오브젝트가 아예 없을경우의 싱글톤
public class BasicSingleton : MonoBehaviour
{
	private static BasicSingleton instance;
    public static BasicSingleton Instance
    {
    	get
        {
        	if(instance == null) 
            {
            	var obj FindFirstObjectByType<BasicSingleton>();
                -> 씬 상에서 오브젝트 찾기
                if (obj != null) -> 씬 상에서 오브젝트 찾음
                	instance = obj; -> 오브젝트 할당
                else -> 씬 상에서 오브젝트를 찾지 못했으면 직접 만들어서 연결
                {
                	var newObj = new GameObject("Basic Singleton");
                    -> 오브젝트 생성
                    newObj.AddComponent<BasicSingleton>();
                    ->컴포넌트 생성
                    instance = newObj.GetComponent<BasicSingleton>();
                    -> 컴포넌트 할당
                }
            }
            
            return instance
    	}
    }
    
    void Awake()
    {
    	if(instance == null)
        {
        	instance = this; -> 싱글턴 인스턴스 할당
            DontDestoryOnLoad(gameObject); -> 씬 전환되도 파괴되지 않도록 설정
        }
        else
        {
        	Destroy(gameObject); -> 중복 방지
        }
    }
}

제너릭 싱글톤

: 싱글톤 제조기를 하나 만들어 두고, 다른 클래스들이 이 틀을 상속받기만 하면 자동으로 싱글톤이 되도록 만든 기법

Generic<T>를 활용한 Singleton
어떤 타입이던지 싱글톤을 생성 가능
public class SingletonCore<T> : MonoBehaviour where T : MonoBehaviour
{
	private static T instance;
    public static T Instance
    {
    	get
        {
        	if(instance == null) 
            {
            	instance == FindFirstObjectByType<T>();
        
                if (instance == null)
                {
                	var newObj = new GameObject(typeof(T).ToString());
                    instance = newObj.AddComponent<T>;
                }
                
            }
            
            return instance
    	}
    }
    
    protected virtual void Awake() -> 이 싱글톤이 Awake문을 사용할 수 도 있으니 선택적으로 오버라이드할 수 있게 생성
    {
    	if(instance == null)
        {
        	instance = this as T; -> 싱글턴 인스턴스 할당
            DontDestoryOnLoad(gameObject); -> 씬 전환되도 파괴되지 않도록 설정
        }
        else
        {
        	Destroy(gameObject); -> 중복 방지
        }
    }
}
해당 클래스를 싱글톤화
public class BasicClass : SingletonCore<BasicClass> -> 싱글톤코어를 상속 <원하는타입>
{
	public int level;
    
    public void LevelUp()
    {
    	level++;
    }
    //만약 Awake를 사용한다면 base.Awake()를 불러준다.
    public void override void Awake()
    {
    	base.Awake(); //부모의 싱글톤 설정 기능을 실행
        level = 1;
    }
}

전략 패턴 (Strategy Pattern)

: 동작을 캡슐화하여 런타임에 알고리즘을 쉽게 교체하는 패턴
1. 인터페이스 정의
2. 전략 구현
3. 컨텍스트(사용자) 설정

1.인터페이스 정의

이동 기능에 대한 인터페이스
public interface IMove
{
	void Move(Transform transform);
}

2.전략구현

달리는 이동 기능
public class MoveRun : IMove
{
	private float speed;
    
    public MoveRun(float speed)
    {
    	this.speed = speed;
    }
    
    public void Move(Transform transform)
    {
    
    }
}
날아서 이동 기능
public class MoveFly : IMove
{
	private float speed;
    
    public MoveRun(float speed)
    {
    	this.speed = speed;
    }
    
    public void Move(Transform transform)
    {
    
    }
}
수영해서 이동 기능
public class MoveSwim : IMove
{
	private float speed;
    
    public MoveRun(float speed)
    {
    	this.speed = speed;
    }
    
    public void Move(Transform transform)
    {
    
    }
}
  1. 컨텍스트(사용자)설정
이동하는 대상
public class Mover : MonoBehaviour
{
	private IMove move;
    
    void Start()
    {
    	move = new MoveRun(3f);
    }
    
    void Update()
    {
    	move.Move(transform); -> start에 transform 주입 시, 생성자 주입 방식이 된다.(너는 평생 이 Transform만 움직여) 
        
        if(Input.GetKeyDown(KeyCode.Shift)
        {
        	move = new MoveRun(3f);
        }
        else if(Input.GetKeyDown(KeyCode.Space)
        {
        	move = new MoveFly(7f);
        }
        else if(Input.GetKeyDown(KeyCode.Tap)
        {
        	move = new MoveSwim(2f);
        }
    }
}

오브젝트 풀 패턴 (Object Pool Pattern)

: 오브젝트를 미리 생성하여 Pool에 저장해두고, 재사용하는 패턴

  • 일반적으로 queue, stack 사용
  • 최적화
public class StudyObjectPool : MonoBehaviour
{
	public Queue<GameObject> objQueue = new Queue<GameObject>();
    
    public GameObject objPrefab;
    -> 풀을 채워 넣을 원본(ex 총알, 몬스터 등)
    public Transform parent;
    -> 생성된 오브젝트들을 깔끔하게 정리하기 위한 폴더 역할의 부모 트랜스폼
    
    void Start()
    {
    	CreateObject(50);
        -> 시작하자마자 50개 미리 생성
    }
    
    private void CreateObject(int amount)
    {
    	for (int i = 0 ; i < amount ; i++)
        {
        	GameObject obj = Instantiate(objPrefab, parent);
            -> 부모 밑에 오브젝트 생성
            EnqueueObject(obj);
            -> 생성 후 창고queue에 넣기
        }
    }
    
    private void EnqueueObject(GameObject newObj) -> 재활용
    {
    	Rigidbody rb = newObj.GetComponent<Rigidbody>();
        
        rb.lineraVelocity = Vector3.zero;
        rb.angularVelocity = Vector3.zero;
        -> 물리적 힘 초기화
        //총알이 발사되기 이전, 상호작용 된것을 초기화
        
        newObj.SetActive(false);
        -> 비활성화
        obj.Queue.Enqueue(newObj);
        -> 창고에 넣기
    }
    
    public GameObject DequeueObject() -> 총알을 발사할 때, DequeueObject 호출을 통해 풀에서 꺼내 쓴다.
    {
    	if(objQueue.Count < 3) -> 자동확장 로직 //준비해둔 50개를 쓰고 3개밖에 안남았을때 불안하기 때문에 즉시 50개 생성
        	CreateObject(50);
        
        GameObject obj = objQueue.Dequeue();
        ->창고에서 꺼내기
        obj.SetActive(true);
        -> 활성화
        
        return obj; -> 배달
    }
  
}
public class PoolController : MonoBehaviour
{
	public StudyObjectPool pool;
    public Transform shootPoint;
    
    void Update()
    {
    	if (Input.GetMouseButtonDown(0))
        {
        	GameObject bullet = pool.DequeueObject();
            -> 풀에서 꺼낸다.(기존의 Instantiate 대체)
            bullet.transform.position = shootPoint.position;
            //총알 발사 기능
        }
    }
}
public class Bullet : MonoBehaviour
{
	public StudyObjectPool pool;
    
    void OnEnable()
    {
    	Invoke("ReturnPool",10f);
        -> 10초 후 해당 오브젝트를 queue에 저장
    }
    private void ReturnPool()
    {
    	pool.EnqueueObject(gameObject);
        -> 오브젝트를 queue에 저장
    }
}

유니티에 내장된 Pool 기능

using UnityEngine.Pool;

public class UnityPoolManager : MonoBehaviour
{
	public ObjectPool<GameObject> pool;
    public GameObject prefab;
    
    void Awake()
    {
    	pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject);
    }
    
    private GameObject CreateObject()
    {
    	GameObject obj = Instantiate(prefab);
        
        return obj;
    }
    
    private void GetObject(GameObject obj)
    {
    	obj.SetActive(true);
    }
    
    private void ReleaseObject(GameObject obj)
    {
    	obj.SetActive(false);
    }
}
public class PoolController : MonoBehaviour
{
	public UnityPoolManager poolManager;
    public Transform shootPoint;
    
    void Update()
    {
    	if(Input.GetMouseButtonDown(0))
        {
        	GameObject bullet = poolManager.pool.Get(); -> pool.Get()을 통해 GetObject()실행, 재고가 없을시 Create하고 Getobject 실행
            bullet.transform.position = shootPoint.position;
        }
    }
}
-> 유니티 풀 내부 함수 pool.Get() 이해하기 쉽게 하기 위한 함수 예시
public GameObject Get()
{
    GameObject item;

    // 1. 재고 확인
    if (재고가_없으면)
    {
        // 이때만 CreateObject가 호출
        item = CreateObject(); 
    }
    else
    {
        // 재고가 있으면 그냥 꺼냅니다.
        item = 창고.꺼내기();
    }

    // 2. 나가기 전에 활성화 (이건 항상 실행됨)
    GetObject(item);

    return item;
}
public class Bullet : MonoBehaviour
{
	public UnityPoolManager poolManager;
    
    void OnEnable()
    {
    	Invoke("ReturnPool", 10f);
	}
    
    private void ReturnPool()
    {
    	poolManager.pool.Release(gameObject);
    }
}

팩토리 패턴 (Factory Pattern)

: 객체 생성을 별도의 클래스(팩토리)에게 위임하고 객체 생성 로직을 캡슐화하여 유연성을 높이는 패턴

  • 다양한 오브젝트, 객체를 복잡하게 생성하지 않고 위임하여 유연성을 높임
  • 상속 구조, 상속 구조가 애매할때는 인터페이스를 주로 사용(IFactory)
  1. 몬스터의 기본 설계도 (상속)
-> 추상 클래스 : 몬스터 뼈대, 몬스터가 가져야 할 기능들
public abstract class MonsterCore
{
	public abstract string Name {get;}
    public abstract void Attack();
}
  1. 구체적인 몬스터 종류
public class Slime : MonsterCore
{
	public override string Name => "슬라임";
    public override void Attack() => Debug.Log("슬라임공격");
}
public class Orc : MonsterCore
{
	public override string Name => "오크";
    public override void Attack() => Debug.Log("오크공격");
}
public class Dragon : MonsterCore
{
	public override string Name => "드래곤";
    public override void Attack() => Debug.Log("드래곤 공격");
}
  1. 공장의 기본 설계도
public abstract class MonsterFactory : MonoBehaviour -> 최상위 팩토리
{
	public MonsterCore Spawn(string type)
    {
    	MonsterCore monster = CreateMonster(type); -> 추상함수랑 연결되어 있음(구체적인 생성은 자식에게 미룸)
    	Debug.Log($"{monster.Name}생성");
        
        return monster;
    }
    
    protected abstract MonsterCore CreateMonster(string type);
    -> 타입에 따라 어떻게 만들지 자식에서 정해라
}
public class ForestSpawner : MonsterFactory
{
	protected override MonsterCore CreateMonster(string type) -> 오버라이드 재정의
    {
    	MonsterCore monster = null;
        switch(type)
        {
        	case : "Slime"
            	monster = new Slime();
                break;
            case : "Orc"
            	monster = new Orc();
                break;
        }
        
        return monster; -> 만든 몬스터 반환
    }
    
    IEnumerator Start() -> 1초마다 랜덤하게 몬스터 생성
    {
    	while(true)
        {
        	int ranIndex = Random.Range(0,2);
            
            string monsterType = ranIndex == 0 ? "Slime" : "Orc";
            Spawn(monsterType);
            
            yield return new WaitForSeconds(1f);
        }
    }
}

명령 패턴 (Command Pattern)

: 명령을 객체로 만들어 캡슐화 하는 것

  • 인터페이스 or 상속

상태 패턴 (State Pattern)

이벤트 버스 패턴 (Event Bus Pattern)

옵저버 패턴 (Observer Pattern)

0개의 댓글