[TIL] 251209

๊น€์„ธํฌยท2025๋…„ 12์›” 9์ผ

โœ๏ธToday I Learned

๐Ÿ“… 2025-12-09

  • ์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ + ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์Šคํƒฏ ํ‘œ์‹œ ๋กœ์ง ๋ถ„๋ฆฌ

์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ + ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์Šคํƒฏ ํ‘œ์‹œ ๋กœ์ง ๋ถ„๋ฆฌ

๐Ÿ“Œ ๋ฌธ์ œ ์ƒํ™ฉ

๋นŒ๋”ฉ/์•„์ดํ…œ ์Šคํƒฏ ํ‘œ์‹œ ์‹œ์Šคํ…œ์—์„œ:

  • FItemData์™€ FBuildingData๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ตฌ์กฐ์ฒด โ†’ ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•จ
  • ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ(TOOL, WEAPON, ARMOR, CONSUMABLE, WALL, FLOOR...)๋งˆ๋‹ค ํ‘œ์‹œํ•  ์Šคํƒฏ์ด ๋‹ค๋ฆ„
  • Switch-case๋กœ ์ฒ˜๋ฆฌํ•˜๋‹ˆ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ณ , ์ƒˆ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ์ฝ”๋“œ ์ˆ˜์ • ํ•„์š”
// Before: ๋„ˆ๋ฌด ๊ธธ๊ณ  ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€
void UpdateCraftingItemStatView(const FItemData& ItemData)
{
    switch (ItemData.Category)
    {
    case EItemCategory::TOOL:
        // ์ฑ„์ทจ ์œ ํ˜•, ๊ณต๊ฒฉ๋ ฅ, ๋‚ด๊ตฌ๋„... 50์ค„
        break;
    case EItemCategory::WEAPON:
        // ๊ณต๊ฒฉ๋ ฅ, ์‚ฌ๊ฑฐ๋ฆฌ, ๋‚ด๊ตฌ๋„... 50์ค„
        break;
    // ...
    }
}

void UpdateBuildingItemStatView(const FBuildingData& BuildingData)
{
    switch (BuildingData.Type) { /* ... */ }
}

๐ŸŽฏ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ + ํŒฉํ† ๋ฆฌ ํŒจํ„ด ์กฐํ•ฉ์œผ๋กœ ํ•ด๊ฒฐ

1. ์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ๋กœ ๋ฐ์ดํ„ฐ ํ†ตํ•ฉ

// IDisplayDataProvider (์ˆœ์ˆ˜ C++ ์ธํ„ฐํŽ˜์ด์Šค)
class IDisplayDataProvider
{
public:
    virtual FGameplayTag GetCategoryTag() const = 0;
    virtual float GetStatValue(const FGameplayTag& StatTag) const = 0;
    // ...
};

// ๊ฐ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ž˜ํ•‘
class FItemDataProvider : public IDisplayDataProvider { /* ... */ };
class FBuildingDataProvider : public IDisplayDataProvider { /* ... */ };

2. ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ Viewer ์ƒ์„ฑ

// ์ถ”์ƒ ํด๋ž˜์Šค
UCLASS(Abstract)
class UStatViewer : public UObject
{
    virtual void ShowStats(
        UListView* ListView,
        const IDisplayDataProvider& DataProvider,
        UGameplayTagDisplaySubsystem* DisplaySystem) PURE_VIRTUAL();
};

// ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ตฌํ˜„
UCLASS() class UToolStatViewer : public UStatViewer { /* ๋„๊ตฌ ์Šคํƒฏ ํ‘œ์‹œ */ };
UCLASS() class UWeaponStatViewer : public UStatViewer { /* ๋ฌด๊ธฐ ์Šคํƒฏ ํ‘œ์‹œ */ };
UCLASS() class UWallStatViewer : public UStatViewer { /* ๋ฒฝ ์Šคํƒฏ ํ‘œ์‹œ */ };

// ํŒฉํ† ๋ฆฌ๊ฐ€ ์ ์ ˆํ•œ Viewer ๋ฐ˜ํ™˜
UCLASS()
class UStatViewerFactory : public UObject
{
    UStatViewer* GetViewer(const FGameplayTag& CategoryTag)
    {
        if (CategoryTag == TAG_Category_Tool)
            return CachedToolViewer;
        // ...
    }
};

3. ์œ„์ ฏ์—์„œ ํ†ตํ•ฉ ์‚ฌ์šฉ

// After: 5์ค„๋กœ ์ถ•์•ฝ, ๋ชจ๋“  ํƒ€์ž… ํ†ตํ•ฉ!
void UItemInfo::UpdateStatViewInternal(const IDisplayDataProvider& DataProvider)
{
    FGameplayTag CategoryTag = DataProvider.GetCategoryTag();
    UStatViewer* Viewer = ViewerFactory->GetViewer(CategoryTag);
    Viewer->ShowStats(ItemStatView, DataProvider, TagDisplaySubsystem);
}

void UpdateCraftingItemStatView(const FItemData& ItemData)
{
    FItemDataProvider Provider(ItemData);
    UpdateStatViewInternal(Provider);
}

void UpdateBuildingItemStatView(const FBuildingData& BuildingData)
{
    FBuildingDataProvider Provider(BuildingData);
    UpdateStatViewInternal(Provider);
}

๐Ÿ”‘ ํ•ต์‹ฌ ํฌ์ธํŠธ

  1. ์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ (IDisplayDataProvider)

    • ์„œ๋กœ ๋‹ค๋ฅธ ๊ตฌ์กฐ์ฒด๋ฅผ ํ†ต์ผ๋œ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผ
    • ์ˆœ์ˆ˜ C++ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ฐ€๋ณ๊ฒŒ ๊ตฌํ˜„
    • ์Šคํƒ์— ์ƒ์„ฑ ๊ฐ€๋Šฅ (์ž„์‹œ ๋ž˜ํผ)
  2. ํŒฉํ† ๋ฆฌ ํŒจํ„ด (UStatViewerFactory)

    • ์นดํ…Œ๊ณ ๋ฆฌ ํƒœ๊ทธ๋กœ ์ ์ ˆํ•œ Viewer ๋ฐ˜ํ™˜
    • Viewer๋“ค์„ ์บ์‹ฑํ•ด์„œ ์žฌ์‚ฌ์šฉ (์„ฑ๋Šฅ)
    • ์ƒˆ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ์‹œ Factory์— ํ•œ ์ค„๋งŒ ์ถ”๊ฐ€
  3. Strategy ํŒจํ„ด (UStatViewer)

    • ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์Šคํƒฏ ํ‘œ์‹œ๋ฅผ ๋…๋ฆฝ ํด๋ž˜์Šค๋กœ
    • ๊ฐ Viewer๊ฐ€ ์ž๊ธฐ ์นดํ…Œ๊ณ ๋ฆฌ ์Šคํƒฏ๋งŒ ๋‹ด๋‹น
    • OCP ์ค€์ˆ˜ (Open-Closed Principle)

๐Ÿ“‚ ํด๋” ๊ตฌ์กฐ

UI/StatDisplay/
โ”œโ”€โ”€ DisplayDataProvider.h           # ์ธํ„ฐํŽ˜์ด์Šค
โ”œโ”€โ”€ Providers/
โ”‚   โ”œโ”€โ”€ ItemDataProvider.h/cpp      # ์•„์ดํ…œ ๋ž˜ํผ
โ”‚   โ””โ”€โ”€ BuildingDataProvider.h/cpp  # ๋นŒ๋”ฉ ๋ž˜ํผ
โ”œโ”€โ”€ Viewers/
โ”‚   โ”œโ”€โ”€ StatViewer.h/cpp            # ์ถ”์ƒ ํด๋ž˜์Šค
โ”‚   โ”œโ”€โ”€ Items/
โ”‚   โ”‚   โ”œโ”€โ”€ ToolStatViewer.h/cpp
โ”‚   โ”‚   โ”œโ”€โ”€ WeaponStatViewer.h/cpp
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ Buildings/
โ”‚       โ”œโ”€โ”€ CraftingStatViewer.h/cpp
โ”‚       โ””โ”€โ”€ ...
โ””โ”€โ”€ StatViewerFactory.h/cpp         # ํŒฉํ† ๋ฆฌ

โœ… ์žฅ์ 

  • ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋กœ ์ฒ˜๋ฆฌ
  • ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ์ง์ด ๋…๋ฆฝ์  (์œ ์ง€๋ณด์ˆ˜ ์‰ฌ์›€)
  • ์ƒˆ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ์ฝ”๋“œ ์ˆ˜์ • ๋ถˆํ•„์š”
  • ๊ฐ Viewer๋ณ„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

๐Ÿ’ก ๋А๋‚€ ์  (What I Felt)

์ฒ˜์Œ์—” switch-case๊ฐ€ ๊ฐ„๋‹จํ•ด ๋ณด์˜€๋Š”๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ๋Š˜์–ด๋‚˜๋ฉด์„œ ํ•จ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด์กŒ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค ๋ž˜ํผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ƒํ™”ํ•˜๊ณ , ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ๊ฐ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋‹ˆ๊นŒ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด์กŒ๋‹ค. ํŠนํžˆ FItemData์™€ FBuildingData๋ฅผ ์ „ํ˜€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  Provider๋กœ๋งŒ ๊ฐ์‹ธ์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ ์‹ ๊ธฐํ–ˆ๋‹ค.

ํŒฉํ† ๋ฆฌ ํŒจํ„ด์ด ์ฒ˜์Œ์—” ์˜ค๋ฒ„์—”์ง€๋‹ˆ์–ด๋ง ๊ฐ™์•˜๋Š”๋ฐ, ์ดํ›„ ๋นŒ๋”ฉ์˜ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‚ด์šฉ๋งŒ ์ˆ˜์ •ํ•  ๋•Œ ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด ํŽธํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ Viewer ํด๋ž˜์Šค๊ฐ€ 30~50์ค„ ์ •๋„๋กœ ์งง์•„์„œ ์ฝ๊ธฐ๋„ ํŽธํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ๋„ ์ข‹์•˜๋‹ค.

UCLASS๋กœ ๊ตฌํ˜„ํ•œ Viewer ํด๋ž˜์Šค๋“ค์ด ์ˆœ์ˆ˜ C++ ์ธํ„ฐํŽ˜์ด์Šค(IDisplayDataProvider)๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ ์ƒˆ๋กœ์› ๋‹ค. UObject ๊ณ„์—ด๊ณผ ์ˆœ์ˆ˜ C++์„ ์กฐํ•ฉํ•ด์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ๋ชฐ๋ž๋Š”๋ฐ, ์Šคํƒ์— ์ƒ์„ฑํ•œ Provider๋ฅผ UObject์ธ Viewer๊ฐ€ ์ฐธ์กฐ๋กœ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ๊น”๋”ํ–ˆ๋‹ค. ์–ธ๋ฆฌ์–ผ์—์„œ๋„ ์ˆœ์ˆ˜ C++๊ณผ ๋””์ž์ธ ํŒจํ„ด์„ ์‹ค์ „์— ์ œ๋Œ€๋กœ ์ ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด์—ˆ๋‹ค.


0๊ฐœ์˜ ๋Œ“๊ธ€