ProjectXZ의 모듈러 시스템에서 UI를 구성하는 과정에서 구조적 종속성 문제가 발생했다. UXZModuleCustomizingLayer(Layer), UXZModuleSelectSlot(Slot), UXZModuleSelectSlotItem(SlotItem), FXZModuleSelectSlotInfo(SlotInfo) 클래스에서 상위와 하위 클래스 간의 불필요한 결합에서 발생하는 종속성 문제와 이를 해결한 과정에 대해서 정리하려고 한다.

SlotItem은 SlotInfo 구조체와 함께 Slot의 데이터를 초기화한다.
SlotItem은 ListView에 들어가는 데이터 항목을 나타내며, SlotInfo는 해당 항목에 대한 구체적인 모듈 정보를 담고 있고, Layer의 UMG에서 데이터가 설정되기 때문에 Layer에 선언되어 있다.
(SlotItem은 Slot에 SlotInfo는 Layer에 선언되어 있음)
하지만 Slot에서 SlotItem을 사용해 데이터를 초기화하는 과정에서 SlotItem이 SlotInfo를 멤버 변수로 가지고 있기 때문에, 간접적으로 Layer에 의존하게 된다.
결국 SlotItem은 Slot의 데이터를 관리하는 역할을 하지만, Layer에 필요한 SlotInfo를 직접 참조하는 구조 때문에, Slot과 Layer 사이의 결합도가 높아진다.
이러한 구조는 순환 참조 문제가 있고 추후에 Slot과 Layer가 독립적으로 확장되거나 변경될 때 문제가 발생할 가능성이 있다.
정리하자면
1. Layer는 ListView로 Slot을 관리
2. SlotItem은 Slot의 데이터를 초기화하기 때문에 Slot에 선언
3. SlotInfo는 Layer에서 Slot에 초기화할 데이터를 넣어주기 때문에 Layer에서 선언
4. 이때 SlotItem이 SlotInfo를 멤버 변수로 가지게 됨 ⇒ 문제의 시작
5. Layer는 상위의 개념이기 때문에 Slot, SlotItem을 알아도 상관 없지만, Slot은 Layer의 하위 개념이기 때문에 Layer에 대한 정보를 알면 안됨
class PROJECTXZ_API UXZModuleSelectSlotItem : public UObject
{
GENERATED_BODY()
public:
...
public:
UXZModuleSelectSlotItem() { }
void InitializeData(FXZModuleSelectSlotInfo NewSlotInfo);
FORCEINLINE const FXZModuleSelectSlotInfo GetSlotInfo() const { return SlotInfo; }
...
private:
void CalculateItemIndexRange();
FXZModuleSelectSlotInfo SlotInfo;
...
};
class PROJECTXZ_API UXZModuleSelectSlotItem : public UObject
{
GENERATED_BODY()
public:
...
public:
UXZModuleSelectSlotItem() { }
void InitializeData(EModularMeshType NewModuleType);
FORCEINLINE const FModuleIndexInfo GetIndexInfo() const { return IndexInfo; }
FORCEINLINE const EModularMeshType GetModuleType() const { return ModuleType; }
private:
...
void CalculateItemIndexRange();
...
EModularMeshType ModuleType;
};
void UXZModuleCustomizingLayer::NativeConstruct()
{
Super::NativeConstruct();
const int ModuleNum = SelectSlotsInfo.Num();
TArray< UXZModuleSelectSlotItem*> SlotsArray;
for ( int i = 0; i < ModuleNum; ++i )
{
UXZModuleSelectSlotItem* SlotItem = NewObject<UXZModuleSelectSlotItem>(this);
SlotItem->InitializeData(SelectSlotsInfo[i].ModuleType);
SlotsArray.Add(SlotItem);
}
SlotListView->SetListItems(SlotsArray);
}
다음부터는 초기 설계 단계에서 이런 계층구조간의 종속성 문제를 미리 생각하여 설계 해야겠다. 지금은 멤버 변수로 포함시키지 않음으로써 간단하게 해결됐지만, 훨씬 복잡한 시스템을 구현해야할 경우,
클래스 간의 결합도를 낮추기 위해 인터페이스를 통해 상위와 하위 간의 의존성을 최소화해야 할 것 같다