게임 개발을 하다 보면 객체의 역할에 따른 명명을 어떻게 해야할 지 모르는 경우가 많다. 협업 프로젝트를 열어 클래스들의 이름을 살펴보면, "Wrapper", "Dispatcher", "Helper", "Manager"로 끝나는 클래스들이 있을 수 있다. 이러한 클래스들은 어떤 역할을 하는지, 그리고 협업을 위해서 어떻게 명명하면 좋은지 알아보도록 하자.
생각보다 내용이 길어질 것 같아 편을 나눠서 작성하겠습니다.
Wrapper
Wrapper: 라이브러리나 API의 인터페이스를 단순화, 혹은 복잡한 시스템을 캡슐화하는 클래스
Wrapper 용어는 "감싸다(Wrap)"라는 의미를 가지고 있다. 즉, 이미 존재하던 Original에 새로운 필드나 메소드, 이벤트 처리, 에러 감지 등 새로운 시스템을 추가하여 감싼 형태라고 생각하면 된다. 이를 통해 시스템을 효율적으로 확장시킬 수 있으며, 유지보수성이 증가한다.
💡 Wrapper 클래스의 유래
Wrapper 객체 지향 프로그래밍에서 데잍와 기능을 감싸는 개념으로 발전했다. 특히 Java에서는 초기 버전에서 기본 타입(int, char 등)을 객체로 표현하기 위해 Wrapper 클래스가 도입되었다. 제네릭(Generic)이 등장하기 이전에는 컬렉션(List, Map 등)에 기본 타입을 직접 담을 수 없었기 때문에, 이를 감싸는 객체로 사용한 것이 Wrapper 클래스이다.
int → Integer
char → Character
double → Double
boolean → Boolean
이를 통해 Java에서는 Wrapper 클래스를 사용하여 Boxing과 Unboxing을 수행한다.
이때부터 Wrapper 클래스는 무언가를 감싸 새로운 기능과 시스템을 제공하는 의미로 쓰이기 시작했다.
사용 예시
- Input Wrapper: 다양한 입력 장치와 플랫폼 간의 차이를 Wrapper 클래스를 통해 추상화할 수 있다.
- Network Wrapper: 서버와 클라이언트 간의 API 호출에 대해 연결 설정, 응답 처리, 에러 감지 등 원하는 기능을 추가하여 Wrapper로 감싸 구현할 수 있다.
- AsyncOperation Wrapper: Unity에서 제공하는 씬 로딩 비동기 작업의 진행 상황(AsyncOperation)을 Wrapper로 감싸 작업에 따른 이벤트를 추상화할 수 있다.
AsyncOperation Wrapper 예시 코드
public class AsyncOperationWrapper
{
private AsyncOperationHandle<SceneInstance> asyncOperation;
public event Action Completed;
public float progress => asyncOperation?.PercentComplete ?? 0f;
public bool IsDone => asyncOperation?.IsDone ?? false;
public void SetAsyncOperation(AsyncOperationHandle<SceneInstance> asyncOperation)
{
this.asyncOperation = asyncOperation;
asyncOperation.Completed += CompleteCallback;
}
private void CompleteCallback(AsyncOperationHandle<SceneInstance> operation)
{
Completed?.Invoke();
}
}
위 예시는 asyncOperation에 이벤트와 처리, 필드를 추가하여 개발자가 관리하기 용이하게 설계된 Wrapper 클래스다. 만약 asyncOperation을 그대로 사용한다면 분산되어 사용되기에 유지 보수가 어려울 것이다.
AsyncOperation Wrapper 클래스를 사용하면 위와 같은 로딩창을 유지보수하기 용이하게 구현할 수 있다.
Dispatcher
Dispatcher: 작업이나 메시지를 특정 스레드로 전달하여 실행하는 클래스
Dispatcher는 주로 UI 관련 작업 혹은 멀티스레드 환경에서 주로 사용한다. 예를 들어, 병렬 프로그래밍 환경에서 메인 스레드에서만 처리하는 로직이 있을 수 있다. 이때 서브 스레드들은 Dispatcher를 통해 이벤트를 메인 스레드에게 전송한다. 그리고 메인 스레드는 받은 이벤트들을 처리한다.
💡 서브 스레드들은 메인 스레드에 Task을 전달한다. 메인 스레드 내부에는 Queue<Task>가 존재하며, 서브 스레드의 요청을 담는다. 메인 스레드는 주기적으로 Queue을 체크하며 요청된 Task을 수행한다.
사용 예시
- UnityMainThreadDispatcher : Unity에서 비동기 작업이나 멀티스레드 작업을 수행한 후, 메인 스레드에서 Unity API를 안전하게 호출할 수 있도록 도와주는 도구이다. Unity 대부분의 API는 메인 스레드에서만 호출할 수 있기 때문에, 서브 스레드는 메인 스레드에 명령을 전달한다.
💡 Unity API에는 게임 오브젝트 생성 및 파괴, 컴포넌트 조작, UI 처리 등이 해당한다. 하지만 이러한 작업들은 메인 스레드에서만 수행되기 때문에, 서브 스레드는 메인 스레드에 명령을 전달하고, 메인 스레드가 그 명령을 받아 명령을 수행한다. 이때 명령을 전달하는 객체를 Dispatcher라고 부른다.
UnityMainThreadDispatcher에 대한 추천글
https://blog.danggun.net/10031
Utility
Utility: 소프트웨어 개발에서 반복적으로 사용되는 기능을 재사용 가능하게 제공하는 클래스
Utility는 보통 특정 비즈니스 로직과는 무관하며, 범용적이고 독립적인 기능을 수행한다. 정적 메서드(static method)로 구성되며, 이를 통해 모든 객체가 쉽게 접근할 수 있다. 이 클래스는 부수 효과(side-effect) 없이 입력을 받아 결과를 반환하는 순수 기능을 제공하는 것이 핵심이다.
💡 Helper로 끝나는 클래스도 있던데, 서로 다른건가?
간혹 프로젝트를 보면 UserHelper와 같은 클래스를 확인할 수 있다. Helper 클래스는 Utility와 마찬가지로 반복적으로 사용되는 기능을 하나로 묶어 메소드를 제공하는 클래스이다. 하지만 용도와 역할에서 차이가 발생한다.
Utility:비즈니스 로직과 무관하게 모든 애플리케이션 전반으로 사용할 수 있는 보조 기능 제공
Helper:특정 기능이나 클래스와 연관된 보조 기능 제공
따라서 Utility가 범용성이 크고, Helper는 범용성이 적다는 특징이 있다.
사용 예시
- StringUtil: 문자열을 처리하는 Utility (Null 체크, 대문자 변환, 특정 문자 찾기 등)
- MathUtil: 수학과 관련된 로직을 처리하는 Utility (최대 및 최소값 찾기 등)
- FileUtil: 파일을 읽고 쓰는 작업을 처리하는 Utility
Random Util 예시 코드
public static T RandomPick<T>(this IEnumerable<T> list)
{
if (list == null) return default(T);
var enumerable = list as T[] ?? list.ToArray();
if (!enumerable.Any()) return default(T);
int pick = UnityEngine.Random.Range(0, enumerable.Length);
return enumerable[pick];
}
위 코드는 확장 메서드를 활용하여 IEnumerable<T> 타입의 컬렉션에서 무작위로 하나의 요소를 선택하는 기능을 제공하는 메서드이다.
정리
오늘은 객체의 역할에 따른 명명에 대해 살펴보았다. 이외에도 관습적으로 사용되는 클래스 이름들이 존재하는데, 이것은 다음 포스트에서 정리하도록 하겠다.
'유니티(Unity) > 이론 정리' 카테고리의 다른 글
[Unity Korea] 객체 지향 설계 원칙 (SOLID) (0) | 2024.11.12 |
---|---|
클래스 이름 짓기 [ 2편 ] + MVC 패턴 (2) | 2024.11.11 |
에셋 번들(Asset Bundle) 이론 정리 (1) | 2024.03.27 |
싱글톤(Singleton) 패턴 (0) | 2024.03.22 |
GetComponent 성능 (1) | 2024.03.16 |