본문 바로가기
TIL

전략 패턴과 SRP로 인벤토리 리팩토링

by vvin39 2025. 5. 22.

내일배움캠프 32일차 TIL

 

UIInventory.cs는 인벤토리 시스템에서 중심적인 역할을 하는 스크립트인데, 지금 상태는 전반적으로 역할이 너무 많고 하나의 클래스에 너무 많은 책임이 몰려있는거 같았다. 이는 SOLID 원칙을 어긴 대표적인 상황이라고 할 수 있다.

 

📌 어떤 SOLID 원칙을 위반했는가?

원칙  위반 내용
S - 단일 책임 원칙(SRP) UI 관리, 아이템 로직 처리, 장비/소비 처리, 드롭 등 너무 많은 책임이 한 클래스에 있음
O - 개방-폐쇄 원칙(OCP) 아이템 타입이 추가되거나 조건이 바뀌면 UIInventory를 직접 수정해야 함
L - 리스코프 치환 원칙(LSP) 위반되진 않았지만, 다양한 아이템 종류가 확장 가능 구조면 더 좋음
I - 인터페이스 분리 원칙(ISP) 적용할 대상 없음 (인터페이스 거의 없음)
D - 의존성 역전 원칙(DIP) CharacterManager.Instance.Player.controller 등 싱글톤에 강하게 의존하고 있음

 

❓ 어떻게 리팩토링하면 좋을까?

1. 책임 분리가 핵심!

주요 책임 그룹

  • UI 표시 담당 → UIInventory
  • 아이템 로직 담당 (추가/제거/장비/소비) → InventoryManager
  • 아이템 선택 상태 관리 → SelectedItemHandler
  • 드롭 로직 처리 → ItemDropper

구조 제안

스크립트  책임
UIInventory UI 창 열고 닫기, 슬롯 갱신 등 UI 관련 처리
InventoryManager 아이템 추가/제거/장비/해제 등 인벤토리 로직
SelectedItemHandler 선택된 아이템 상태 관리 및 UI 업데이트
ItemDropper 아이템 버리기(드롭) 처리
InventoryManager
├── AddItem()
├── RemoveItem()
├── EquipItem()
├── UnEquipItem()
└── HasItem()

InventoryUIController
├── SetSlots()
├── ShowSelectedItem()
└── ToggleUI()

SelectedItemHandler
├── SelectItem(index)
├── ClearSelectedItem()
└── GetSelectedItem()

ItemDropper
└── DropItem(data, position)

 

 

 

2. SOLID 원칙에 맞는 리팩토링 방향

원칙 리팩토링 방향
SRP 각 책임을 별도 클래스 또는 구성요소로 분리
OCP 아이템 종류가 늘어나도 로직을 분기 없이 확장 가능하도록 Strategy 또는 인터페이스 사용
DIP CharacterManager.Instance 대신 DI를 고려하거나 인스펙터에서 참조 주입
테스트 용이성 ↑ 각 기능 유닛 테스트도 가능해짐

 

일부 코드 예시: 소비 아이템 처리 리팩토링

//기존 코드

if (selectedItem.item.type == ItemType.Consumable)
{
    for (int i = 0; i < selectedItem.item.consumables.Length; i++)
    {
        switch (selectedItem.item.consumables[i].type)
        {
            case ConsumableType.Health:
                condition.Heal(selectedItem.item.consumables[i].value); break;
            case ConsumableType.Hunger:
                condition.Eat(selectedItem.item.consumables[i].value); break;
        }
    }
}

개선 방향 (전략 패턴 느낌)

public interface IConsumableEffect
{
    void Apply(PlayerCondition condition, int value);
}

public class HealEffect : IConsumableEffect
{
    public void Apply(PlayerCondition condition, int value) => condition.Heal(value);
}

public class HungerEffect : IConsumableEffect
{
    public void Apply(PlayerCondition condition, int value) => condition.Eat(value);
}

이렇게 하면 ConsumableEffectFactory를 통해 해당 타입에 맞는 효과 클래스를 반환받고, UIInventory는 단순히 effect.Apply()만 호출하면 된다.

 

리팩토링 전후 비교 요약

항목  리팩토링 전  리팩토링 후
UIInventory 책임 UI, 소비, 장비, 드롭 등 모두 처리 UI 관련만 담당
효과 처리 방식 switch 문으로 직접 처리 인터페이스 기반 전략 패턴
확장성 아이템 종류 추가 시 코드 수정 필요 OCP를 지켜 쉽게 확장 가능

 

💡 느낀 점

  • 기존의 UIInventory.cs는 비대한 책임과 결합도가 많은 상태이다.
  • 각 기능을 분리해 SOLID 원칙을 지키고 유지보수성을 높이는 것이 좋다고 판단했다.
  • 리팩토링을 진행하는 것이 너무 복잡해 보일 수 있지만, 기능 단위로 클래스를 나누고 의존성을 낮추는 리팩토링을 천천히 진행하니 구조를 파악하기 더 쉬워졌다.

 

 

 

'TIL' 카테고리의 다른 글

AnimationCurve로 구현한 낮과 밤 그리고 온도  (0) 2025.05.27
[Unity] ScriptableObject  (1) 2025.05.23
[Unity] ImageType  (1) 2025.05.21
[Unity] 델리게이트 (Delegate)  (1) 2025.05.20
[Unity] Raycast  (0) 2025.05.19

최근댓글

최근글

skin by © 2024 ttuttak