ProjectDDD/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md

321 lines
8.8 KiB
Markdown
Raw Normal View History

2025-08-20 06:22:08 +00:00
# 기존 UI 시스템을 MVVM으로 마이그레이션 가이드
Unity 프로젝트에서 기존 UI 시스템(BaseUi, BasePopupUi, PopupUi)을 MVVM 패턴으로 점진적으로 전환하는 방법을 설명합니다.
## 호환성 보장
### 1. 기존 UI 시스템은 그대로 유지됩니다
- `BaseUi`, `BasePopupUi`, `PopupUi<T>` 클래스들은 변경되지 않음
- 기존 UI들은 계속해서 정상 동작
- `UiManager``PopupUiState`도 기존 방식 그대로 지원
### 2. 새로운 Integrated 클래스들 사용
- `IntegratedBaseUi<TViewModel>` : BaseUi + MVVM 기능 통합
- `IntegratedBasePopupUi<TViewModel>` : BasePopupUi + MVVM 기능 통합
- `IntegratedPopupUi<TInputEnum, TViewModel>` : PopupUi<T> + MVVM 기능 통합
## 마이그레이션 전략
### 단계 1: 점진적 전환 계획 수립
1. **우선순위 결정**
```
높음: 복잡한 상태 관리가 필요한 UI (InventoryView, ShopView 등)
중간: 중간 규모의 팝업 UI
낮음: 간단한 확인/알림 다이얼로그
```
2. **전환 범위 설정**
- 한 번에 하나의 UI씩 전환
- 관련된 ViewModel과 Service 함께 구현
- 철저한 테스트 후 다음 UI 진행
### 단계 2: ViewModel 및 Service 구현
#### 기존 View 분석
```csharp
// 기존 InventoryView
public class InventoryView : MonoBehaviour
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _items;
public void UpdateCategoryView(InventoryCategoryType category)
{
_currentCategory = category;
// 복잡한 UI 업데이트 로직
}
}
```
#### ViewModel로 분리
```csharp
// 새로운 InventoryViewModel
public class InventoryViewModel : SimpleViewModel
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _visibleItems;
public InventoryCategoryType CurrentCategory
{
get => _currentCategory;
set => SetField(ref _currentCategory, value);
}
public void SetCategory(InventoryCategoryType category)
{
CurrentCategory = category;
UpdateVisibleItems(); // 비즈니스 로직
}
}
```
#### Service 계층 분리
```csharp
// 비즈니스 로직을 Service로 분리
public class InventoryService : IUiService
{
public IEnumerable<ItemViewModel> FilterItems(InventoryCategoryType category)
{
// 복잡한 필터링 로직
}
}
```
### 단계 3: View를 MVVM으로 전환
#### 기존 View 코드
```csharp
public class InventoryView : MonoBehaviour, IEventHandler<InventoryChangedEvent>
{
[SerializeField] private Transform _slotParent;
[SerializeField] private Text _categoryLabel;
// 많은 상태 변수들과 복잡한 로직들...
}
```
#### MVVM View로 전환
```csharp
public class MvvmInventoryView : MvvmBasePopupUi<InventoryViewModel>
{
[SerializeField] private Transform _slotParent;
[SerializeField] private Text _categoryLabel;
protected override void SetupBindings()
{
BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText));
// 간단한 바인딩 설정만
}
public void OnCategoryButtonClicked(int categoryIndex)
{
ViewModel.SetCategory((InventoryCategoryType)categoryIndex);
}
}
```
### 단계 4: 마이그레이션 체크리스트
#### ViewModel 체크리스트
- [ ] SimpleViewModel 상속
- [ ] 모든 상태를 속성으로 변환
- [ ] SetField 사용한 PropertyChanged 알림
- [ ] 계산된 속성 구현
- [ ] 비즈니스 로직을 Service로 위임
#### View 체크리스트
- [ ] 적절한 MVVM Base 클래스 상속
- [ ] SetupBindings() 구현
- [ ] UI 이벤트를 ViewModel 메서드 호출로 변경
- [ ] 직접적인 UI 업데이트 코드 제거
- [ ] HandleCustomPropertyChanged에서 복잡한 UI 처리
#### Service 체크리스트
- [ ] IService 인터페이스 구현
- [ ] 복잡한 비즈니스 로직 포함
- [ ] 데이터 접근 로직 캡슐화
- [ ] 테스트 가능한 구조
## 구체적인 마이그레이션 예시
### 예시 1: 간단한 팝업 UI
#### Before (기존)
```csharp
public class SimpleDialogUi : BasePopupUi
{
[SerializeField] private Text _messageText;
[SerializeField] private Button _confirmButton;
public void ShowMessage(string message)
{
_messageText.text = message;
_confirmButton.onClick.AddListener(Close);
}
}
```
#### After (MVVM)
```csharp
// ViewModel
public class DialogViewModel : SimpleViewModel
{
private string _message;
public string Message
{
get => _message;
set => SetField(ref _message, value);
}
}
// View
public class MvvmSimpleDialogUi : MvvmBasePopupUi<DialogViewModel>
{
[SerializeField] private Text _messageText;
[SerializeField] private Button _confirmButton;
protected override void SetupBindings()
{
BindText(_messageText, nameof(DialogViewModel.Message));
}
protected override void Awake()
{
base.Awake();
_confirmButton.onClick.AddListener(() => Close());
}
}
```
### 예시 2: 복잡한 목록 UI
#### Before (기존 InventoryView)
```csharp
public class InventoryView : MonoBehaviour
{
private InventoryCategoryType _currentCategory;
private Dictionary<string, ItemSlotUi> _slotLookup = new();
public void UpdateCategoryView(InventoryCategoryType category)
{
_currentCategory = category;
// 복잡한 필터링 로직
foreach (var slot in _slotLookup.Values)
{
bool shouldShow = MatchesCategory(slot.Model, category);
slot.SetActive(shouldShow);
}
// 정렬 로직
// UI 업데이트 로직
}
private bool MatchesCategory(ItemViewModel model, InventoryCategoryType category)
{
// 복잡한 카테고리 매칭 로직
}
}
```
#### After (MVVM)
```csharp
// ViewModel (상태 관리)
public class InventoryViewModel : SimpleViewModel
{
private InventoryCategoryType _currentCategory;
private List<ItemViewModel> _visibleItems;
public InventoryCategoryType CurrentCategory
{
get => _currentCategory;
set => SetField(ref _currentCategory, value);
}
public List<ItemViewModel> VisibleItems
{
get => _visibleItems;
private set => SetField(ref _visibleItems, value);
}
public string CategoryDisplayText => GetCategoryDisplayText();
public void SetCategory(InventoryCategoryType category)
{
CurrentCategory = category;
UpdateVisibleItems();
OnPropertyChanged(nameof(CategoryDisplayText));
}
private void UpdateVisibleItems()
{
var filtered = _inventoryService.FilterItems(CurrentCategory, CurrentSortType);
VisibleItems = filtered.ToList();
}
}
// Service (비즈니스 로직)
public class InventoryService : IUiService
{
public IEnumerable<ItemViewModel> FilterItems(InventoryCategoryType category, InventorySortType sortType)
{
// 복잡한 필터링과 정렬 로직을 Service로 이동
}
}
// View (UI 바인딩만)
public class MvvmInventoryView : MvvmBasePopupUi<InventoryViewModel>
{
[SerializeField] private Text _categoryLabel;
[SerializeField] private Transform _slotParent;
protected override void SetupBindings()
{
BindText(_categoryLabel, nameof(InventoryViewModel.CategoryDisplayText));
}
protected override void HandleCustomPropertyChanged(string propertyName)
{
if (propertyName == nameof(InventoryViewModel.VisibleItems))
{
UpdateItemSlots(); // 복잡한 UI 업데이트는 여전히 필요
}
}
}
```
## 마이그레이션 주의사항
### 1. 기존 코드와의 의존성
- 기존 UI를 참조하는 다른 코드들 확인
- 이벤트 시스템과의 연동 유지
- ScriptableObject 설정 파일들과의 호환성
### 2. 성능 고려사항
- PropertyChanged 이벤트의 과도한 발생 방지
- UI 업데이트 배칭 활용
- 메모리 누수 방지 (이벤트 핸들러 정리)
### 3. 테스트 전략
- ViewModel 단위 테스트 작성
- 기존 UI와 새 UI 동시 테스트
- 사용자 시나리오 기반 통합 테스트
## 마이그레이션 우선순위 추천
### 1차 마이그레이션 (높은 우선순위)
- **InventoryView**: 복잡한 상태 관리, 필터링, 정렬
- **ShopView**: 상품 목록, 카테고리, 구매 로직
- **MenuView**: 메뉴 관리, 레시피 선택
### 2차 마이그레이션 (중간 우선순위)
- **SettingsView**: 다양한 설정 옵션들
- **StatisticsView**: 데이터 표시와 계산
### 3차 마이그레이션 (낮은 우선순위)
- **SimpleDialogUi**: 간단한 확인 다이얼로그들
- **NotificationUi**: 알림 팝업들
이 가이드를 따라 점진적으로 UI를 MVVM으로 전환하면, 기존 시스템의 안정성을 유지하면서도 새로운 아키텍처의 이점을 얻을 수 있습니다.