전략 패턴이란?

정의: 알고리즘(행동, 동작)을 각각의 클래스로 정의하고, 이들을 교체할 수 있도록 만드는 패턴.

목표: 행동을 컨텍스트 객체와 분리해, 실행 중에도 전략을 쉽게 변경할 수 있도록 함.

 

구성 요소 클래스 예시 역할 설명
Strategy IAttackStrategy 행동(공격)의 인터페이스 정의. 구체 전략을 위한 공통 틀 제공
ConcreteStrategy MeleeAttack, RangedAttack, MagicAttack 실제로 공격을 구현하는 클래스들
Context Enemy 전략을 사용하는 객체. 공격 전략을 저장하고 실행함
Client GameManager 전략을 설정해주는 역할 (전략 선택 및 컨텍스트에 주입)

 

IAttackStrategy – 전략 인터페이스

public interface IAttackStrategy
{
    void Attack();
}

역할

모든 공격 전략이 따라야 할 공통 메서드 정의

이 인터페이스를 기반으로 여러 공격 방식이 교체 가능해짐

 

MeleeAttack, RangedAttack, MagicAttack – 구체 전략 클래스들

public class MeleeAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("근접 공격: 칼을 휘두릅니다.");
    }
}

public class RangedAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("원거리 공격: 활을 쏩니다.");
    }
}

public class MagicAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("마법 공격: 파이어볼을 발사합니다.");
    }
}

역할

각 클래스는 Attack() 메서드를 자기 방식대로 구현

외부에서 어떤 클래스가 사용되든, Attack()만 호출하면 내부 구현이 실행됨

새로운 전략(예: 폭탄 공격)을 추가하려면 클래스 하나만 더 만들면 됨 — OCP(Open/Closed Principle) 충족

 

Enemy – 컨텍스트 클래스 (전략을 사용하는 주체)

public class Enemy : MonoBehaviour
{
    private IAttackStrategy attackStrategy;

    public void SetAttackStrategy(IAttackStrategy strategy)
    {
        attackStrategy = strategy;
    }

    public void PerformAttack()
    {
        attackStrategy?.Attack();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            PerformAttack();
        }
    }
}

역할

외부에서 전달받은 전략(SetAttackStrategy)을 저장

PerformAttack()을 호출하면 현재 설정된 전략을 실행

클라이언트(예: GameManager)가 전략만 바꾸면 동작이 달라짐 → 컨텍스트는 바뀌지 않음

 

GameManager – 클라이언트 (전략을 정하는 역할)

public class GameManager : MonoBehaviour
{
    public Enemy enemy;

    void Start()
    {
        enemy.SetAttackStrategy(new MagicAttack()); // 원하는 전략 설정
    }
}

역할

전략을 선택하고 컨텍스트(Enemy)에 주입

전략 패턴의 장점은 여기서 발휘됨: 조건에 따라 전략을 바꾸기만 하면 됨

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

싱글톤 패턴  (0) 2025.05.02
오브젝트 풀링 패턴  (0) 2025.03.08

싱글톤 패턴이란?

싱글톤 패턴은 클래스의 인스턴스를 하나만 생성하고,

그 인스턴스를 어디서든 접근할 수 있도록 전역적인 접근점을 제공하는 패턴입니다.

게임에서 GameManager, AudioManager, UIManager처럼 한 개만 존재해야 하는 객체를 만들 때 주로 사용됩니다.

 

구현

유일성 보장: 추가 인스턴스 생성을 막아야 하므로 Awake()에서 중복을 제한합니다.

전역 접근: public static 속성을 사용해 언제 어디서든 접근할 수 있게 합니다.

 

기본 형태

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject); // 중복 방지
        }
    }

    public void GameStart()
    {
        Debug.Log("Game Started!");
    }
}

 

사용 예시

public class GameManagerUser : MonoBehaviour
{
    void Start()
    {
        GameManager.Instance.GameStart();
    }
}

 

확장형 싱글톤 패턴

싱글톤을 여러 매니저 클래스에 사용할 경우, 매번 같은 코드를 반복해야 하는 단점이 있습니다.
이를 해결하기 위해 제너릭(Generic) 을 활용한 확장형 싱글톤 패턴을 사용할 수 있습니다.

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<T>();
                if (instance == null)
                {
                    GameObject obj = new GameObject();
                    instance = obj.AddComponent<T>();
                }
            }
            return instance;
        }
    }
    private void Awake()
    {
        if (instance == null)
        {
            instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
    }
}
public class GameManager : Singleton<GameManager>
{
    public void GameStart()
    {
        Debug.Log("Game Started!");
    }
}

 

Singleton<T> 클래스를 한 번 만들어두면,

다른 매니저 클래스들은 상속만으로 싱글톤 기능을 바로 사용할 수 있어

코드 중복을 줄이고 유지보수가 쉬워집니다.

 

 

 

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

전략 패턴  (0) 2025.05.04
오브젝트 풀링 패턴  (0) 2025.03.08

오브젝트 풀링을 사용하는 이유

Instantiate, Destory를 반복하면서 조각난 메모리가 쌓인다.

 

이러한 조각난 메모리를 해결하기 위해서 가비지 컬렉터(GC)가 실행된다.

 

가비지컬렉터 (garbage collector)

1) 메모리 관리를 담당하는 시스템

2) 메모리에서 더 이상 사용하지 않는 객체를 제거하여 메모리 회수하는 기능이 있다.

 

이 가비지 컬렉터가 실행되면서 게임에 버벅임이 발생하는데

메모리가 많을경우 많은 끊김 현상이 발생한다.

 

이러한 것을 방지하기 위한 최적화 기법이 오브젝트 풀링이다.

 

오브젝트 풀링

오브젝트 풀링은 게임 오브젝트를 생성 또는 파괴하지 않는다.

필요한 만큼 미리 생성해두고, 비활성화

파괴(Destroy) 대신 오브젝트를 비 활성화하여 풀에 추가

생성(Instantiate) 대신 풀에서 꺼낸 뒤 활성화한다.

더보기
public class ObjectManager : MonoBehaviour
{
    // 풀에 대한 정보를 담는 구조체 정의
    // 프리팹, 크기, 풀에 대한 키 값을 저장
    [System.Serializable]
    public class Pool
    {
        public string key; // 오브젝트 풀의 고유 이름 (예: "Enemy", "Bullet")
        public GameObject prefab; // 생성할 오브젝트의 프리팹
        public int size; // 풀에 생성할 오브젝트의 개수
    }

    // 여러 풀을 관리하는 리스트 (Inspector에서 설정)
    public List<Pool> poolList;

    // 각 풀을 이름별로 관리하는 딕셔너리
    // key: 풀의 이름 (key), value: 해당 이름에 맞는 오브젝트 큐(Queue)
    private Dictionary<string, Queue<GameObject>> PoolDictionary = new Dictionary<string, Queue<GameObject>>();

    
    private void Awake()
    {
        // poolList에 저장된 풀 정보를 이용하여 각각의 풀을 초기화
        foreach (var pool in poolList)
        {
            Queue<GameObject> queue = new Queue<GameObject>(); // 큐를 생성하여 오브젝트들을 담을 준비

            // 풀 크기만큼 오브젝트를 생성하고 큐에 비활성화된 상태로 넣음
            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                queue.Enqueue(obj); 
            }

            // 생성한 큐를 PoolDictionary에 추가 (key는 pool.key)
            PoolDictionary.Add(pool.key, queue);
        }
    }

    // 오브젝트 풀에서 비활성화된 오브젝트를 꺼내 활성화하고 반환
    public GameObject GetObject(string key)
    {
        // 해당 key에 해당하는 큐가 존재하고, 큐에 오브젝트가 있다면
        if (PoolDictionary.ContainsKey(key) && PoolDictionary[key].Count > 0)
        {
            // 큐에서 오브젝트를 하나 꺼냄 (FIFO)
            GameObject obj = PoolDictionary[key].Dequeue();
            obj.SetActive(true); // 꺼낸 오브젝트를 활성화
            return obj; // 꺼낸 오브젝트 반환
        }

        // 오브젝트가 없으면 null 반환
        return null;
    }

    // 오브젝트를 풀에 다시 반환 (비활성화 후 큐에 넣기)
    public void ReturnObject(string key, GameObject obj)
    {
        // 해당 key에 대한 큐가 존재하는지 확인
        if (!PoolDictionary.ContainsKey(key)) return;

        // 오브젝트를 비활성화하고 다시 큐에 넣어 재사용할 수 있게 함
        obj.SetActive(false);
        PoolDictionary[key].Enqueue(obj);
    }
}



 

인스펙터창에 해당하는 프리팹과 타입, 개수를 할당해준다.

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

전략 패턴  (0) 2025.05.04
싱글톤 패턴  (0) 2025.05.02

+ Recent posts