내일배움캠프 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 |