ProjectDDD/Assets/_DDD/_Scripts/GameUi/New/MIGRATION_GUIDE.md
2025-08-20 15:22:08 +09:00

8.8 KiB

기존 UI 시스템을 MVVM으로 마이그레이션 가이드

Unity 프로젝트에서 기존 UI 시스템(BaseUi, BasePopupUi, PopupUi)을 MVVM 패턴으로 점진적으로 전환하는 방법을 설명합니다.

호환성 보장

1. 기존 UI 시스템은 그대로 유지됩니다

  • BaseUi, BasePopupUi, PopupUi<T> 클래스들은 변경되지 않음
  • 기존 UI들은 계속해서 정상 동작
  • UiManagerPopupUiState도 기존 방식 그대로 지원

2. 새로운 Integrated 클래스들 사용

  • IntegratedBaseUi<TViewModel> : BaseUi + MVVM 기능 통합
  • IntegratedBasePopupUi<TViewModel> : BasePopupUi + MVVM 기능 통합
  • IntegratedPopupUi<TInputEnum, TViewModel> : PopupUi + MVVM 기능 통합

마이그레이션 전략

단계 1: 점진적 전환 계획 수립

  1. 우선순위 결정

    높음: 복잡한 상태 관리가 필요한 UI (InventoryView, ShopView 등)
    중간: 중간 규모의 팝업 UI
    낮음: 간단한 확인/알림 다이얼로그
    
  2. 전환 범위 설정

    • 한 번에 하나의 UI씩 전환
    • 관련된 ViewModel과 Service 함께 구현
    • 철저한 테스트 후 다음 UI 진행

단계 2: ViewModel 및 Service 구현

기존 View 분석

// 기존 InventoryView
public class InventoryView : MonoBehaviour
{
    private InventoryCategoryType _currentCategory;
    private List<ItemViewModel> _items;
    
    public void UpdateCategoryView(InventoryCategoryType category)
    {
        _currentCategory = category;
        // 복잡한 UI 업데이트 로직
    }
}

ViewModel로 분리

// 새로운 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 계층 분리

// 비즈니스 로직을 Service로 분리
public class InventoryService : IUiService
{
    public IEnumerable<ItemViewModel> FilterItems(InventoryCategoryType category)
    {
        // 복잡한 필터링 로직
    }
}

단계 3: View를 MVVM으로 전환

기존 View 코드

public class InventoryView : MonoBehaviour, IEventHandler<InventoryChangedEvent>
{
    [SerializeField] private Transform _slotParent;
    [SerializeField] private Text _categoryLabel;
    
    // 많은 상태 변수들과 복잡한 로직들...
}

MVVM View로 전환

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 (기존)

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)

// 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)

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)

// 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으로 전환하면, 기존 시스템의 안정성을 유지하면서도 새로운 아키텍처의 이점을 얻을 수 있습니다.