wpf viewmodel 접근과 싱글턴패턴

안녕하세요.

Aview와 BView가 있고 각각 뷰모델이 있습니다.
commnunityToolkit nuget을 사용하였고

app.xaml.cs 에서

services.AddSingleton();
services.AddSingleton();

를 통하여 싱글턴패턴으로 만들었습니다

이떄 bviewmodel에 a라는 프로퍼티를 aview에서 접근하려면 일반적으로 어떤 방식을 쓰나요?

2개의 좋아요

image   ChatGPT

AViewModelBViewModel이 서로 다른 뷰모델이고, AViewModelAVIew와 연결되어 있고, BViewModelBView`와 연결되어 있다고 가정하겠습니다.


AViewModel에서 BViewModel에 대한 참조를 유지하고 BView에 대한 참조를 가져오려면 다음과 같은 방법을 사용할 수 있습니다.

  1. AViewModel에서 BViewModel을 생성하고 BViewModel에 대한 참조를 유지합니다.
public class AViewModel : ObservableObject
{
    private BViewModel _bViewModel;

    public AViewModel(BViewModel bViewModel)
    {
        _bViewModel = bViewModel;
    }

    public BViewModel BViewModel
    {
        get { return _bViewModel; }
        set { SetProperty(ref _bViewModel, value); }
    }

    // ...
}

AViewModel 생성자에서 BViewModel을 인스턴스화하고, BViewModel에 대한 참조를 _bViewModel 필드에 유지합니다. 이후 BViewModel에 대한 참조를 BViewModel 프로퍼티를 통해 노출합니다.

  1. BViewModelIServiceProvider를 사용하여 가져오고 AViewModel에 전달합니다.
public class AViewModel : ObservableObject
{
    private BViewModel _bViewModel;

    public AViewModel(IServiceProvider serviceProvider)
    {
        _bViewModel = serviceProvider.GetService<BViewModel>();
    }

    public BViewModel BViewModel
    {
        get { return _bViewModel; }
        set { SetProperty(ref _bViewModel, value); }
    }

    // ...
}

IServiceProvider를 사용하여 BViewModel 인스턴스를 가져올 수 있습니다. 이를 위해 AViewModel 생성자에서 IServiceProvider를 받아옵니다. GetService<BViewModel>()를 호출하여 BViewModel 인스턴스를 가져옵니다.

이렇게하면 AViewModelBViewModel이 서로 독립적인 인스턴스로 유지되면서, AViewModelBViewModel에 대한 참조를 가져올 수 있습니다.

2개의 좋아요

감사합니다. 그러면 aview에서 bviewmodel의 프로퍼티를 쓰려면 (가령 바인딩을 한다던가를 위해) aview에서는 별다른 코드의 추가 없이 bviewmodel의 프로퍼티를 쓰면되나요? 만약에 프로퍼티의 이름이 겹친다거나 하면 어떤식으로 해결할까요?

2개의 좋아요

@jamesnet214 님의 답변 기준으로 보면
그냥 BViewModel.속성 으로 바인딩 path를 사용하거나 하시면 되죠.,
(그런데 방법이 좋아 보이진 않습니다.)

2개의 좋아요

혹시 다른 방법도 있을까요? 찾아볼 키워드라도 주시면 공부해보겠습니다!

3개의 좋아요

commnunityToolkit 을 쓰신다고 했는데

개인적으론 싱글턴 관리가 정말 필요한지 다시 한번 생각해서

싱글턴은 지양하고, 메신저를 이용해서 데이터 전달 및 수신 방법으로 하는 것 이 좋을 것 같습니다.

5개의 좋아요

감사합니다. 사실 메신저를 쓰다가 메신저가 많아지면 성능이 떨어진다는 얘기를 들어서 다른 방법을 찾고 있었습니다

3개의 좋아요

메신저가 내부적으론 이벤트어그리게이트 패턴 방식으로

중계자가 구독자에게 통보 하는 일반 적으로도 많이 사용 되는 걸로 알 고 있는데

구독과 해지 부분만 잘 처리 하신다면 성능이 떨어질 이유는 없다고 생각 합니다.

관련해서

방식도 제공하고 있구요

어떤 이유로 인해 성능이 안좋은 것인지 알고 계신가요?

성능이 안좋다 = 메모리 관점에서 보면

오히려 싱글턴 관리가 메모리 낭비면에서 안좋아 보입니다.

5개의 좋아요

감사합니다! 공부한번해보겠습니다

3개의 좋아요

문제요구사항에 적어주시진 않았지만, AView와 BView가 서로 종속 관계(Control안에 Control이 들어있는 상태) 라면 제 생각에는 AViewModel과 BViewModel은 서로 종속관계여야한다고 생각합니다. (그래야 뷰모델을 바인딩했을 때 종속된 View에도 ViewModel을 바인딩하기 편하기 때문)

따라서 위 조건대로 종속관계라면 ChatGPT 답변이 괜찮은 것 같습니다.

하지만 AView와 BView가 서로 다른 레이아웃 상에 있고 종속관계가 아니라면 위의 @aroooong 답변대로 메신저를 사용하는 게 맞을 것 같습니다.

하지만 저도 역시, ViewModel을 Singleton으로 설정해야하는가는 다시 한번 고려해보시면 좋을 거 같다는 의견입니다.

3개의 좋아요

성능은 뭐…
아무래도 인스턴스에 직접 접근하는 것보다야 느릴 수밖에 없겠죵?
(하지만 미미하겠… =ㅅ=;:wink:

그래서 결국 이러저러한 이유 때문에 메신저를 사용하지 않는다면
DI 에 등록된 인터페이스로 인스턴스를 획득해 사용하는 방법이 제일 적절하겠죵?
그렇다묜 @jamesnet214 님이 chatGPT 한테 받은 답변대로 하면 될 거 같슴다.

다만
AViewModel 과 BViewModel 사이의 종속성 여부에 따라, setter 노출 여부를 결정해야할 거 같아요.
그리고 클래스 인스턴스를 직접 할당하는 것보다는 인터페이스를 이용한 injection 이 더 좋겠네요.

그게 아니면
아예 aview 의 DataContext 를 BViewModel 로 할당해서 사용하는 방법을 쓸 수도 있겠네요.
전체 BViewModel 할당이 부담스러우면 BViewModel 에서 접근해야할 일부 속성만 노출하는 인터페이스를 사용해 처리하는 것도 괜찮을 거 같아요.

방법은 많은 거 같슴다.
이리저리 헤딩하다보면 적절한 선택을 할 수 있지 않을까 싶어용 ㅇㅅㅇ/

4개의 좋아요

image  Jamesnet214


@Greg.Lee 위 내용을 잘 읽어보고 분석해서 적절한 샘플 코드와 설명을 잘 만들어봐봐 단 마크다운 양식으로 만들고 샘플코드에는 주석도 꼼꼼히!


image  ChatGPT


네, 이전에 제안한 여러 가지 방법 중에서는 DI를 이용한 인터페이스로 인스턴스를 주입하는 것이 가장 적절한 방법으로 보입니다. 그리고 AViewModel과 BViewModel 사이의 종속성 여부에 따라 setter 노출 여부를 결정하면 됩니다. 클래스 인스턴스를 직접 할당하는 것보다는 인터페이스를 이용한 injection이 좋습니다.

그리고 DataContext를 이용한 방법도 가능합니다. 아예 aview의 DataContext를 BViewModel로 할당해서 사용하는 방법도 있고, 전체 BViewModel 할당이 부담스러우면 BViewModel에서 접근해야할 일부 속성만 노출하는 인터페이스를 사용하는 방법도 있습니다. 방법은 많지만, 이리저리 헤딩하다보면 적절한 선택을 할 수 있을 것입니다.

아래는 샘플 코드입니다. DataContext를 이용한 방법과 DI를 이용한 방법 두 가지를 보여드리겠습니다. 주석을 꼼꼼히 읽어보시면 이해하기 쉬울 것입니다.

// BViewModel에서 사용할 인터페이스
public interface IMyService
{
    void DoSomething();
}

// IMyService 인터페이스를 구현한 클래스
public class MyService : IMyService
{
    public void DoSomething()
    {
        // ...
    }
}

// AViewModel에서 사용하는 클래스
public class AViewModel : ViewModelBase
{
    // BViewModel 인스턴스
    private BViewModel _bViewModel;

    // DataContext를 이용한 방법
    public AViewModel()
    {
        // DataContext를 BViewModel으로 할당
        _bViewModel = new BViewModel();
        DataContext = _bViewModel;
    }

    // DI를 이용한 방법
    public AViewModel(BViewModel bViewModel)
    {
        _bViewModel = bViewModel;
    }

    // BViewModel에 대한 Setter
    public void SetBViewModel(BViewModel bViewModel)
    {
        _bViewModel = bViewModel;
    }
}

// BViewModel에서 사용하는 클래스
public class BViewModel : ViewModelBase
{
    // IMyService 인터페이스를 구현한 MyService 클래스의 인스턴스
    private readonly IMyService _myService;

    // IMyService 인터페이스를 DI로 주입 받음
    public BViewModel(IMyService myService)
    {
        _myService = myService;
    }

    // AViewModel에서 호출할 메서드
    public void DoSomething()
    {
        _myService.DoSomething();
    }
}

// ViewModelBase 클래스는 MVVM 패턴에서 사용하는 클래스로, 
// INotifyPropertyChanged 인터페이스를 구현합니다.
public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?
            .Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

위의 코드에서 IMyService 인터페이스를 구현한 MyService 클래스는 실제로 BViewModel에서 사용할 서비스입니다.

2개의 좋아요

음… 역시…=ㅅ=;;;

그럴싸한 말을 하긴 하는데
코드는 뭔가 참고 할 수 있는 수준은 아니군요…

좀 더 공부를 시켜야할 듯… ㅋㅅㅋ

4개의 좋아요