티스토리 뷰
Today I Learned - Day 62 [클래스 생성을 string으로 하기]
불면증 도사 2024. 12. 10. 21:36프로그래밍을 하다 보면 '기능을 모르면 아예 연상도 못하는' 경우가 더러 있다.
만약에 event를 몰랐다고 가정 해 보자.
코딩 자체가 클래스 간 자료 교환을 함수 호출으로 하는 것이 기초적인 방법인 탓에 event를 안쓰고도 대부분의 기능은 작성이 가능하다. 반대로 말하면, '별 다른 요소 없이, event를 자연스럽게 의식하는 것은 어렵다'라고도 말할 수 있을 것이다.
[물론, event 자체는 워낙 중요한 시스템이다 보니, 이런저런 곳에서 많이 나와서 이럴 일이 그다지 없긴 하다.]
즉, 기존에 가능한 부분을 더욱 간단하게, 명료하게 정리할 수 있는 부분은 정말로 알아차리기 어렵다.
오늘은 본인 기준으로는 그러했던 기능 중 하나를 설명하고자 한다.
'한 변수에 Enum값에 따라 다른 자식 클래스를 할당해야 하는 경우'를 생각해 보자.
일반적으로는 Enum값에 따른 switch-case로 작업하는 것이 떠오를 것이다.
Enum값 자체를 자료형으로 사용하는 것을 생각해 낸다고 해도, 현 시점에서 '변수를 변수형으로 사용하는 것'은 불가능하기 때문에 결과적으로는 switch - case로 귀결되는 경우도 많다.
그렇다면, 이 기능을 핀포인트로 할 수 있게 해 주는 기능은, 과연 없는 것일까.
사실, 이 기능 자체가 코드의 확장성을 어마어마하게 늘려주기 때문에 없을 가능성은 거의 없다.
정답 부터 말하자면, '있다.' 그것도 확실하게 이 현상을 해결하기 위해서 사용하는 기능이 있다.
그럼, 지금 부터 그 기능을 알아보도록 하자.
동적 클래스 생성 : Activator.CreatIstance()
참고 URL : https://learn.microsoft.com/ko-kr/dotnet/api/system.activator.createinstance?view=net-8.0
상술한 상황에서 다들 한번 쯤 해봤던 생각이 '변수형을 지정하는 변수를 사용하면 안되나?'일 것이다.
물론, Type 타입으로 변수를 만들어서 사용하는 것 자체는 가능하다. 다만, 이를 이용해서 변수형을 지정하는 건 또 '변수가 변수형으로 지정될 수가 없다'는 점에 막히게 된다.
제너릭으로 만드는 건 해당 스크립트 전체를 아우르는 대규모 수정을 동반할 가능성이 높아서 시행하기는 부담스러운 게 사실고, 그렇지 않더라도 제너릭 값에 원하는 타입을 어떻게 넣을지에서 위와 동일한 문제점이 발생하게 된다.
그렇다면 깔끔하게 해결할 수 있는 방법은 없는 것일까.
이에 대한 해답이 바로 Activator.CreateInstance()이다.
사용법은 간단하다.
var typeValue = Type.GetType(typeName); // typeName을 타입으로 변경
/* 인자 없이 생성 */
var typeClass = Activator.CreateInstance(typeValue);
var typeClass = Activator.CreateInstance(typeValue, Object[]); // 인자 전달하며 생성
대체로 이런 방식으로 사용하면 된다.
[사실 이것 이외에도 여러 형태가 있다. 다만, 제너릭은 '자료형'이 들어가야 하기에 본문의 상황에서는 못쓴다는 것에 주의할 것.]
다만, 이렇게 해서 typeClass로 들어가는 인스턴스의 자료형은 Object이기 떄문에 반드시 캐스팅을 해 줘야한다.
일반적으로는 상술한 선택적 동적 생성이 필요한 상황이면 같은 인터페이스나 부모 클래스를 상속받고 있는 게 보통이므로, 이 인터페이스 혹은 부모 클래스로 캐스팅 해서 처리하는 편.
자 그럼, 기초적인 부분이라 다들 알고 있겠지만, 캐스팅 방식에 대해서 알아보자.
이러한 방법에도 2가지가 있는데
- 캐스팅하는 과정을 추가하는 방법
- type typeObject = typeClass as type;
- 저 함수 자체에서 캐스팅하는 방법
- var typeClass = (type)Activator.CreateInstance(typeValue);
각자 장단점이 달라서 용도가 다르다고 보는 것이 좋다.
1번은 이후에 if(typeObject == null)을 통해서 예외상황 검출이 가능하다. 대신 라인이 더 늘어나기도 하고, 활용하는 변수의 양도 하나 더 증가한다는 단점이 있다. 물론, 어지간하면 크게 치명적인 단점은 아닌 편.
반대로 2번은 문장이 간단하게 끝나지만 null 여부에 따른 동작을 정의할 수가 없다. 그래서 '확실하게 null이 아닌 경우'에 사용하는 것이 좋은 구성.
이 방법을 이용하면 bridge 패턴이나 기타 디자인 패턴들을 활용하는 게 더욱 깔끔하고 수월해지게 된다.
번외. new type()랑 뭐가 다를까?
상기한 내용으로 끝나면 다소 아쉬울 터다.
그러니 준비했다. 유사 방식과의 비교다. 이번 경우에는 new()에 해당한다.
사실, 이것은 제너릭을 사용하는가에 따라서 또 나뉜다.
- new type();
- new T();
- Activator.CreateInstance<type>();
- Activator.CreateInstance<T>();
대략적으로 이런 순서로 빠르다고 생각하면 된다.
단, 2번과 3번은 속도가 엇비슷해서 사실상 공동 2등이라고 보는 게 좋다.
1번은 2|3번에 비해서 압도적으로 빠르고, 4번은 2|3번 보다 약간 느리다.
즉, new()를 쓸 수 있는 상황이라면 Activator.CreateInstance()를 남용하는 것은 좋지가 않다.
필요할 때에만 쓰도록 하자.
이번 내용은 함께 작업하는 팀원이 해당 함수를 써서 작업을 했길래, 개인적으로 더 알아보고 작성한 내용이다.
솔직히 개발하면서 저런 '필요한' 클래스를 포괄적으로 제작할 수 있는 구조는 개발에 익숙해질 수록 써먹을 부분이 많기 때문에, 이번 내용은 정말로 유익한 내용이라고 생각하고 있다.
디자인 패턴을 사용할 때에는 저런 '동적'으로 '원하는' 요소를 생성할 수 있어야 하는 경우가 많아서 그것 만으로도 충분히 도움이 되지 않을까 생각한다.
switch-case로 하면 가독성이 심히 구리기도 하고...
'스파르타 내일배움캠프 > Today I Learned' 카테고리의 다른 글
Today I Learned - Day 64 [간과하기 쉬운 실수들에 대하여] (0) | 2024.12.12 |
---|---|
Today I Learnd - Day 63 [리플렉션] (0) | 2024.12.11 |
Today I Learned - Day 61 [디자인 패턴 - 2] (1) | 2024.12.09 |
Today I Learned - Day 60 [Get Component 최적화] (0) | 2024.12.06 |
Today I Learned - Day 59 [디자인 패턴 - 1] (1) | 2024.12.05 |