Q.제네릭을 왜 사용하는걸까?
A.간단히 말해서 클래스를 자료형에 상관없이 자유롭게 쓸 수 있게 하는것
즉, 클래스를 변수처럼 사용 하고 싶을때 주로 사용한다.
정수,문자열 데이터 를 담는공간을 변수라 하고
함수를 변수처럼 사용하면 delegate를 사용하고
class를 변수처럼 사용하기 위해 제네릭을 사용한다.
이런 느낌으로 보면 된다.
Q.제네릭을 쓰면 뭐가 좋아?
A. 클래스에 자료형 제약을 걸고 사용 하여 처음 구조 설계시 시간이 걸리지만
유지 보수에 매우 효율적이기 때문이다.
Q.어떨때 써??
A. 자주 사용되는 클래스인데 자료형이 다양한 클래스인경우에 주로 사용된다.
첫번쨰 예시 ) 탄막슈팅게임에서 총알,아이템,이펙트 등등 다양하게 객체를 생성해야 되는 류의 게임에서
오브젝트 풀링을 제너릭 구조로 설계해서 사용하기
자료형을 담을 오브젝트 Pool을 선언해 준다. 그리고 제약조건으로 MonoBehaviour를 선언하여 오브젝트 및 컴포넌트로 사용 할수 있는 class만 T로 선언 할 수있다.
public class ObjectPool<T> where T : MonoBehaviour
{
public Queue<T> PoolQueue = new Queue<T>();
public bool InitPushObject(T poolObject)
{
if (poolObject == null)
{
Debug.Log(poolObject.name + "이 null 입니다.");
return false;
}
poolObject.gameObject.SetActive(false);
PoolQueue.Enqueue(poolObject);
return true;
}
public T PoolObject(Vector2 pos)
{
if (PoolQueue.Count <= 5)
{
Debug.Log(typeof(T).Name + "오브젝트 갯수 부족!");
return null;
}
T obj = PoolQueue.Dequeue();
obj.gameObject.transform.position = pos;
obj.gameObject.SetActive(true);
return obj;
}
public void PushObject(T pushObj)
{
pushObj.gameObject.SetActive(false);
PoolQueue.Enqueue(pushObj);
//Debug.Log("총알 큐 갯수"+PoolQueue.Count);
}
}
T로 해당 자료형을 선언하고 Dictionary의 value 값으로 ObjectPool<T>를 선언하여
SpawnManager도 제네릭으로 설계한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class SpawnManager<T> : MonoBehaviour, iManager where T : MonoBehaviour,iPoolable<T>
{
//부모 클래스를 저장
[SerializeField] protected List<T> prefabesList;
protected Dictionary<string, ObjectPool<T>> objectPools = new Dictionary<string, ObjectPool<T>>();
public virtual void Initialize() { }
}
결국 최종적으로 사용하는 EnemySpanwManager에 SpawnManager<EnemyController>를 상속하여 제네릭으로 생성하였다. 이렇게되면 자료형만 바꿔 작성하면 Item,Effect 생상선이 올라가기 때문이다.
또한 이 구조에서 T는 부모 클래스를 넣고서 EnemyController의 자식 클래스로 형변환하여 확장성도 확보가 가능해진다.
public class EnemySpawnManager : SpawnManager<EnemyController>
{
[SerializeField] private ItemSpawnManager itemSpawnManager;
[SerializeField] private EffectManager effectManager;
public override void Initialize()
{
Debug.Log(gameObject.name + "Initalize 완료!");
}
private void Awake()
{
EnemyController gameObj;
foreach (EnemyController prefab in prefabesList)
{
GameObject poolContainer = new GameObject("Pool_Container_" + prefab.name);
ObjectPool<EnemyController> objectPool = new ObjectPool<EnemyController>();
for (int i = 0; i < prefab.EnemySO.PoolCount; i++)
{
gameObj = Instantiate(prefab, poolContainer.transform);
gameObj.OnEventDieObject += GameManager.Instance.GetScore;
gameObj.OnEventPushObject += PushObject;
gameObj.OnEventDropItem += itemSpawnManager.RandomPoolObject;
gameObj.OnEventDieEffectObject += effectManager.PoolObject;
objectPool.InitPushObject(gameObj);
}
objectPools.Add(prefab.EnemySO.enemyName, objectPool);
}
Initialize();
}
public EnemyController PoolObject(string objName, Vector2 spawnPos)
{
return objectPools[objName].PoolObject(spawnPos);
}
public EnemyController PoolObject(EnemyController enemy, Vector2 spawnPos)
{
return objectPools[enemy.EnemySO.enemyName].PoolObject(spawnPos);
}
private void PushObject(EnemyController enemy)
{
//objectPools[testmonster.monsterName].PushObject(testmonster);
objectPools[enemy.EnemySO.enemyName].PushObject(enemy);
}
}
예시2) 싱글톤 구조를 제네릭으로 구성
싱글톤 구조는 Manager 클래스에서 정말 많이 쓰이기에 제네릭으로 구조를 잡고 상속으로 사용하게되면
효율성이 매우증가한다.
이 외에도 많은 사용용도가 있지만 나는 이 목적으로 많이 사용을 했던것 같다.