mvvm패턴에서 view와 viewmodel에 대한 질문

최근들어 질문을 많이 올리게 되네요;;;

제가 생각하는 방식이 mvvm 패턴을 위배되는 방식인지 궁금합니다.

먼저 상황을 설명드리면 버튼을 눌렀을 때 해당 뷰에 있는 윈도우가 변해야 된다고 가정을 하겠습니다. 그런데 뷰모델은 당연히 뷰에 대한 참조가 있어선 안되잖아요. 그래서 다들 많은 방식을 사용하시던데 제가 생각하는 방식은 안되는 것인지 싶어 질문 올립니다.

먼저 인터페이스를 하나 만듭니다.

public interface INavigation
    {
        void OnSizeChanged();
    }

그리고 이 인터페이스를 뷰와 뷰 모델 둘 다 상속을 합니다.
뷰 부분입니다.

public partial class MainWindow : Window, INavigation
    {
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = MainViewModel(this);
        }

        public void OnSizeChganged()
        {
            //구현
        }
    }

뷰 모델 입니다.

internal class ViewModel : ViewModelBase
    {
        private INavigation _nav;
        public ViewModel(INavigation nav)
        {
            _nav = nav;
            btnMaximized = new Command(btnMaximizedMethod, CanExecuteMethod);
        }

        public ICommand btnMaximized { get; set; }

        private void btnMaximizedMethod(object obj)
        {
            _nav.OnSizeChganged();
        }

        private bool CanExecuteMethod(object arg)
        {
            return true;
        }
    }

이런 방식으로 해서 뷰에서 뷰 모델을 연결할 때 인터페이스를 넘겨서 뷰 모델에서 인터페이스의 함수를 호출하는 방식으로 실행하는건 mvvm 방식에 위배되는 방식인가요?

좋아요 3

저도 그렇게 합니다. ViewModel이 View 타입을 참조하지 않게 하면 됩니다. 저는 이렇게 써요.

관련된 저의 생각은 다음과 같습니다.

좋아요 4

이 관계를 프로젝트 관계로 강제(?) 하려면,

View와 ViewModel을 각각 별도의 프로젝트로 만들고

View 프로젝트는 ViewModel 프로젝트를 참조하되
ViewModel 프로젝트는 View 프로젝트를 참조하지 않도록 하면 됩니다.

좋아요 3

저도 개인적인 생각입니다. :slight_smile:

단편적으로 구조적으로만 보면 위배되는 것은 아닙니다만!

그러나,

저렇게 되면 View 프로젝트가 ViewModel 프로젝트와 강력한 커플링이 발생됩니다.

즉 개념적 구조로 보았을땐 View는 ViewModel에 오류가 있는 경우

정상 빌드 및 실행 하지 못하게 됩니다.

View를 단순한 껍데기로 보고 UI관련 비지니스로직을 ViewModel로 보았을때

UI쪽 비지니스 로직에 문제가 있다 한들

껍데기에 불구한 View는 그냥 있는 그대로 실행될 수 있어야 하고 어떤 로직에도 관여되어서는 안되는게

MVVM 목적에 있어 제일 베스트라고 생각합니다.

개인적으로는 말씀하신 방법처럼 하셔도 되고 MVVM 위반은 아니지만
베스트는 아니라고 말씀드리고 싶습니다.

좋아요 3

감사합니다. 어떤 사람들은 이 방식에 부정적인 의견인 분들도 있었거든요. 하지만 이렇게 안하면 다른 mvvm 프레임워크를 많이 사용해야 될 듯 해서, 전 공식 지원이 아닌 프레임워크는 최대한 사용하지 말자라는 주의거든요. 설령 공식 지원이라도 가능하면 최대한 안쓰려는 주의라서 ㅎㅎ

위배되지 않는다니 다행이네요. 감사합니다.

좋아요 2

확실히 생각해보니 그렇네요.

그렇다면 혹시 aroooong 님께서는 어떤 방법을 주로 사용하시는지 알 수 있을까요?
wpf는 이번에 처음해서 다른 분들이 어떤 방식으로 많이들 사용하는지 알고 싶습니다.

좋아요 2

여담이지만 저도 프레임워크를 지양하는 방식으로 오래 해오고 있습니다. 분명 거기서 오는 장점이 확실히 있는 것 같아요. 하지만 반대로 부작용도 생길 수도 있을 것 같습니다. 모든 상황에 다 들어맞는 방법론은 없기 때문에 다른 방식도 관심 있게 보시면 지금 만들고 계신 구조가 더 강력해질 것 같습니다.!!

그리고 @mincook 질문자님 구조에서 DI(IoC) 요소를 첨가하면 INavigation 처럼 필요에 따라 View 인터페이스를 다양하게 만들어 사용하면 더 재밌는 구조가 될 것 같네요. :smile:

좋아요 3

질문 내용의 코드중 왜 INavigation를 ViewModel에 넘기는지 생각해볼 필요가 있습니다.

ViewModel에서 화면 이동과 같은 화면제어를 하기 위함이라면

서비스, 비헤이비어, 바인딩 등을 통해 얼마든지 인터페이스 없이 처리 할 수 있습니다.

두번째로는
View에서 this.DataContext = new ViewModel()
로 사용되는 순간 저의 답변 내용과 같이

View는 ViewModel과 강력한 참조가 발생될 수 밖에 없습니다.

이런 문제를 회피하려면

View의 구성은 ViewModel 그 자체를 바인딩 하는것으로만 UI를 꾸며야 합니다.

즉 화면의 구성은 ViewModel덩어리로 그 자체로 구성되어야 하고
이렇게 되면 CAB(Composite UI Application Block)형태의 개발로 발전됩니다.

저도 초보입장이라 예전에 최대한 이런 구조로 만들어 보았던 샘플 예제를 공유해봅니다.

※ 다른 고수분들의 GitHub 소스를 검색해보시는것도 도움이 됩니다.

좋아요 6

정말 감사합니다. 덕분에 많은 도움이 될 듯 합니다.

좋아요 3

개인적으로 Window 클래스에 INavigation 인터페이스를 확장하도록 하여 ViewModel에 참조를 전달하는 방식이 MVVM 구조에 위배된다고 생각하지 않습니다. ISP에도 부합 되고, 테스트 가능해 괜찮은 방식이라는 생각이 드네요.

다만, 좀더 느슨한 구조와 재사용성을 높이기 위해 몇가지 다른 방식들을 제안드려 봅니다.
첫번째는 바인딩을 이용한 방식입니다.
이 방식은 상태 기반으로 동작하기 때문에 상태 변화에 따른 콜백을 미리 등록하여 처리 가능하도록 구성해야 합니다.
image

두번째는 구성하시려는 INavigation과 비슷한 형태인데, View를 간접 참조하도록 별도의 인터페이스를 구성하는 방식입니다.
이 방식과 생각하신 INavigation과의 차이점이라면 View를 간접 참조하게 구성할 수 있고, 대상 뷰가 아닌 다른 뷰들도 참조할 수 있어 공통화된 코드 수행이 용이하고 SRP를 준수하여 코드 재사용성이 높다고 볼수 있습니다.

세번째는 Messenger를 활용한 이벤트 기반 처리 방식입니다.
ViewModel 이벤트를 발행하고, 이를 구독하고 있는 View에서 후 처리를 진행합니다.
대부분의 MVVM 프레임워크들은 Event Aggregator를 필수적으로 구현하고 있습니다.
그래서 아주 Common한 방식으로 이해해주셔도 될 것 같네요.
View와 ViewModel의 생명주기가 다른 경우 메모리 누수가 발생 될 위험이 있는데 대부분의 Messenger는 WeakReference 방식으로 참조를 관리하여 이 문제를 해결합니다.

이외에는 사용자 정의 EventTriggerAction, 사용자정의 Behavior 등이 있습니다.

좋아요 11