종류(type)을 클래스가 아니라 객체로 표현하는 패턴이다.
원래 상속으로 표현하던 분류 체계를, 인스턴스가 타입 객체를 참조하는 형태로 바꾼다.
Unity 의 scriptable object가 타입 객체 패턴의 대표적인 예시다.
기존 enum 으로 관리하는 데이터들을 타입객체 패턴으로 전환할 수 있다.
public class CurrencyType
{
public string Id { get; }
public string IconName { get; }
public string DisplayName { get; }
private CurrencyType(string id, string iconName, string displayName) { ... }
public static readonly CurrencyType Gold = new("gold", "icon_gold", "골드");
public static readonly CurrencyType Gem = new("gem", "icon_gem", "보석");
}
enum 과 비교해서 type 객체 패턴으로 currency를 관리하면 다음과 같은 장점이 있다.
1.데이터가 타입과 함께 산다.
enum 일때는 currencyType.Gold는 그냥 정수 0이였고, 아이콘, 표시명 등 부속 정보는 전부 바깥의 switch 문이나 dictionary에 흩어져 있다.
// 전
string GetIcon(CurrencyType t) => t switch {
CurrencyType.Gold => "icon_gold",
CurrencyType.Gem => "icon_gem",
...
};
// 후
currency.IconName
2.동작도 붙일 수 있다
데이터뿐 아니라 메서드나 델리게이트를 필드로 가질 수 있다. Format(long amount) 같은걸 타입 객체가 직접 처리하면 호출부에서 분기가 사라진다.
그러나 타입객체 패턴을 적용하려면 감수해야하는 지점이 있었다.
1.switch 패턴 매칭이 안된다.
C#의 switch 문으로 사용하려면 if / else if 체인이나 switch {} 표현식의 when 절로 바꿔야 한다.
2.직렬화가 자동이 아니다.
enum은 인스팩터에서 드롭다운으로 뜨고 세이브에 정수로 들어가지만, 순수 클래스는 Unity 인스팩터에 노출되지 않고 JsonUtility 로도 안된다.
세이브에서는 id 문자열만 쓰고, 로드시 ID -> 인스턴스로 되돌리는 조회 경로가 필요하다.
private static readonly Dictionary<string, CurrencyType> _byId = ...;
public static CurrencyType FromId(string id) => _byId[id];
그래서 생각했던 방법이 id 자체는 enum으로 유지하는 거였다.
앞서 말했던 enum의 역할을 크게 2개라고 생각했는데,
이 패턴의 전환 목적은 2번을 없애는거지 1번이 아니라고 판단했다.
public enum CurrencyId { Gold, Gem, Ticket }
public class CurrencyType
{
public CurrencyId Id { get; }
public string IconName { get; }
public string DisplayName { get; }
public bool IsHardCurrency { get; }
// ...
}
Unity 에서는 직렬화와 인스팩터 비용을 계산하면 위 처럼 관리하는게 더 낫다는 생각을 하였다.
이미 운영중인 서비스 이기도 했고.