2009-04-23 3 views
26

MVVM을 처음 사용하면서 실험을하고 있습니다. 물론 모든 디자인 패턴은 모든 문제를 해결할뿐입니다. 그래서 응용 프로그램 상태를 저장하는 위치와 응용 프로그램 전체 명령을 저장할 위치를 파악하려고합니다.응용 프로그램 설정/상태를 MVVM 응용 프로그램에 저장하는 위치

내 응용 프로그램이 특정 URL에 연결됩니다. ConnectionWindow와 ConnectionViewModel을 사용하여 사용자로부터이 정보를 수집하고 명령을 호출하여 주소에 연결할 수 있습니다. 다음 번에 응용 프로그램이 시작될 때 사용자에게 묻지 않고이 동일한 주소에 다시 연결하려고합니다.

내 솔루션은 특정 주소에 연결하고 해당 주소를 일부 영구 저장소 (실제로 저장된 위치는이 질문과 관련이 없음)에 저장하는 명령을 제공하는 ApplicationViewModel을 만드는 것입니다. 다음은 축약 된 클래스 모델입니다.

응용 프로그램보기 모델 :

public class ApplicationViewModel : INotifyPropertyChanged 
{ 
    public Uri Address{ get; set; } 
    public void ConnectTo(Uri address) 
    { 
     // Connect to the address 
     // Save the addres in persistent storage for later re-use 
     Address = address; 
    } 

    ... 
} 

연결보기 모델 :

public class ConnectionViewModel : INotifyPropertyChanged 
{ 
    private ApplicationViewModel _appModel; 
    public ConnectionViewModel(ApplicationViewModel model) 
    { 
     _appModel = model; 
    } 

    public ICommand ConnectCmd 
    { 
     get 
     { 
      if(_connectCmd == null) 
      { 
       _connectCmd = new LambdaCommand(
        p => _appModel.ConnectTo(Address), 
        p => Address != null 
        ); 
      } 
      return _connectCmd; 
     } 
    }  

    public Uri Address{ get; set; } 

    ... 
} 

그래서 질문은 이것이다 : ApplicationViewModel이 처리 할 수있는 권리 방법이 있나요? 응용 프로그램 상태를 저장할 수있는 다른 방법은 무엇입니까?

편집 : 이것이 테스트 가능성에 어떤 영향을 미치는지 알고 싶습니다. MVVM을 사용하는 주된 이유 중 하나는 호스트 응용 프로그램없이 모델을 테스트 할 수 있다는 것입니다. 특히, 중앙 집중식 앱 설정이 테스트 가능성에 영향을 미치고 종속 모델을 조롱하는 기능에 대한 통찰력에 관심이 있습니다.

답변

10

M-V-VM을 사용하지 않는 경우 솔루션은 간단합니다.이 데이터와 기능을 응용 프로그램 파생 유형에 넣습니다. 그런 다음 Application.Current를 사용하여 액세스 할 수 있습니다. 여기서 알 수 있듯이, Application.Current는 ViewModel을 테스트 할 때 문제가 발생합니다. 그것이 바로 고쳐 져야 할 부분입니다. 첫 번째 단계는 구체적인 응용 프로그램 인스턴스에서 자신을 분리하는 것입니다. 인터페이스를 정의하고 구체적인 응용 프로그램 유형에 구현하여이 작업을 수행하십시오.

public interface IApplication 
{ 
    Uri Address{ get; set; } 
    void ConnectTo(Uri address); 
} 

public class App : Application, IApplication 
{ 
    // code removed for brevity 
} 

이제 다음 단계는 제어 또는 서비스 로케이터의 반전을 사용하여 뷰 모델 내에서 Application.Current에 대한 호출을 제거하는 것입니다.

public class ConnectionViewModel : INotifyPropertyChanged 
{ 
    public ConnectionViewModel(IApplication application) 
    { 
    //... 
    } 

    //... 
} 

모든 "글로벌"기능은 조만간 서비스 인터페이스 인 IApplication을 통해 제공됩니다. 올바른 서비스 인스턴스로 ViewModel을 생성하는 방법은 여전히 ​​남아 있지만 이미 처리하고있는 것처럼 들릴까요? 거기에 솔루션을 찾고 있다면 Onyx (면책 조항, 필자는 저자)가 솔루션을 제공 할 수 있습니다. 귀하의 응용 프로그램은 View.Created 이벤트에 가입하고 자체 서비스로 추가하고 프레임 워크는 나머지를 처리합니다.

+0

저는 실제로 WPF에 대한 통찰력을 얻기 위해 지난 며칠 동안 Onyx 코드를 쏟아 부 었습니다. 그것은 내가 생각하는 방법과 내가 배웠던 것을 확실히 배치했다. –

+0

감사합니다. Onyx 자체를 사용하지 않더라도 아이디어가 유용하기를 바랍니다. Onyx는 분명 여기서는 필요하지 않지만, 내가 생각하는 서비스 인터페이스 솔루션은 당신이 찾고있는 것입니다. – wekempf

2

예, 올바른 길을 가고 있습니다. 시스템에서 데이터를 전달해야하는 두 개의 제어가있는 경우 가능한 한 분리 된 방식으로 제어를 수행하려고합니다. 이를 수행하는 데는 여러 가지 방법이 있습니다.

프리즘 2에서는 "데이터 버스"와 비슷한 영역을 가지고 있습니다. 하나의 컨트롤은 버스에 추가 된 키를 사용하여 데이터를 생성 할 수 있으며 해당 데이터가 변경 될 때 해당 데이터에서 콜백을 등록 할 수있는 모든 컨트롤을 생성 할 수 있습니다.

저는 개인적으로 "ApplicationState"라고하는 것을 구현했습니다. 그것은 같은 목적을 가지고 있습니다. INotifyPropertyChanged를 구현하면 시스템의 모든 사용자가 특정 등록 정보에 쓰거나 변경 이벤트에 등록 할 수 있습니다. Prism 솔루션보다 덜 일반적이지만 작동합니다. 이것은 당신이 만든 것입니다.

하지만 이제 응용 프로그램 상태를 전달하는 방법에 대한 문제가 있습니다. 이 일을하는 오래된 학교 방식은 그것을 싱글 톤으로 만드는 것입니다. 나는 이것에 대한 큰 팬이 아니다.

public interface IApplicationStateConsumer 
{ 
    public void ConsumeApplicationState(ApplicationState appState); 
} 

트리의 모든 시각적 요소는이 인터페이스를 구현하고, 단순히 뷰 모델에 응용 프로그램 상태를 통과 할 수 : 대신,이 인터페이스는 다음과 같이 정의했습니다.

루트 창에서 Loaded 이벤트가 발생하면 시각적 트리를 가로 질러 앱 상태 (IApplicationStateConsumer)를 원하는 컨트롤을 찾습니다. 나는 그들에게 appState를 건네고, 나의 시스템은 초기화된다. 그것은 가난한 사람의 의존성 주입입니다.

반면에 Prism은 이러한 모든 문제를 해결합니다. 나는 다시 돌아가서 프리즘을 사용하여 다시 건축 할 수 있었으면 좋겠다. 그러나 비용면에서 나에게 너무 늦다.

11

다른 모델과 직접 통신하는 하나의보기 모델이있는 코드에 대해 일반적으로 나빠질 수 있습니다. 필자는 패턴의 VVM 부분이 기본적으로 플러그 가능해야하며 코드 영역 내에서는 그 영역 내의 다른 부분의 존재에 의존해서는 안된다는 생각을 좋아합니다. 이 논리를 뒷받침하는 이유는 논리를 중앙 집중화하지 않으면 책임을 정의하기 어려워 질 수 있기 때문입니다.

한편 실제 코드를 기반으로 ApplicationViewModel의 이름이 잘못 지정되었거나 뷰에서 모델에 액세스 할 수 없으므로 단순히 이름을 잘못 선택했을 수 있습니다.

어느 쪽이든 해결책은 책임을 깨뜨리는 것입니다.

  1. 사용자가
  2. 해당 주소를 지속 서버에 연결이 주소를 사용하여 주소
  3. 에 연결을 요청하도록 허용 : 내가 볼때 당신은 달성하기 위해 세 가지가 있습니다.

나는 두 개의 클래스 대신에 세 개의 클래스가 필요하다고 제안합니다.

public class ServiceProvider 
{ 
    public void Connect(Uri address) 
    { 
     //connect to the server 
    } 
} 

public class SettingsProvider 
{ 
    public void SaveAddress(Uri address) 
    { 
     //Persist address 
    } 

    public Uri LoadAddress() 
    { 
     //Get address from storage 
    } 
} 

public class ConnectionViewModel 
{ 
    private ServiceProvider serviceProvider; 

    public ConnectionViewModel(ServiceProvider provider) 
    { 
     this.serviceProvider = serviceProvider; 
    } 

    public void ExecuteConnectCommand() 
    { 
     serviceProvider.Connect(Address); 
    }   
} 

다음으로 결정할 사항은 주소가 SettingsProvider에 도착하는 방법입니다. 현재와 ​​마찬가지로 ConnectionViewModel에서 전달할 수 있지만보기 모델의 결합이 증가하고 ViewModel에서 지속성을 유지해야하는 책임이 없으므로 관심이 없습니다. 또 다른 옵션은 ServiceProvider로부터 전화를하는 것이지만 ServiceProvider의 책임이 될 것 같은 느낌이 들지 않습니다. 사실 그것은 SettingsProvider가 아닌 다른 사람의 책임처럼 느껴지지 않습니다. 따라서 설정 제공자가 연결된 주소의 변경 사항을 청취하고 개입하지 않고 유지해야한다고 생각하게됩니다.즉 이벤트가 :

public class ServiceProvider 
{ 
    public event EventHandler<ConnectedEventArgs> Connected; 
    public void Connect(Uri address) 
    { 
     //connect to the server 
     if (Connected != null) 
     { 
      Connected(this, new ConnectedEventArgs(address)); 
     } 
    } 
} 

public class SettingsProvider 
{ 

    public SettingsProvider(ServiceProvider serviceProvider) 
    { 
     serviceProvider.Connected += serviceProvider_Connected; 
    } 

    protected virtual void serviceProvider_Connected(object sender, ConnectedEventArgs e) 
    { 
     SaveAddress(e.Address); 
    } 

    public void SaveAddress(Uri address) 
    { 
     //Persist address 
    } 

    public Uri LoadAddress() 
    { 
     //Get address from storage 
    } 
} 

이것은 내가 this question에 대한 답변에서 언급 한 당신은 가능하면 피하려고 내가 여기 EventAggregator를 사용하려는 ServiceProvider과 SettingsProvider 간의 긴밀한 결합을 도입

테스트 가능성 문제를 해결하기 위해 이제 각 메서드에서 수행 할 작업에 대해 매우 명확한 기대치가 있습니다. ConnectionViewModel이 connect를 호출하면 ServiceProvider가 연결되고 SettingsProvider가 지속됩니다. 당신은 아마 인터페이스에 클래스에서 ServiceProvider에 커플 링을 변환 할 ConnectionViewModel를 테스트하려면 다음을 보장하기 위해 확인할 수

public class ServiceProvider : IServiceProvider 
{ 
    ... 
} 

public class ConnectionViewModel 
{ 
    private IServiceProvider serviceProvider; 

    public ConnectionViewModel(IServiceProvider provider) 
    { 
     this.serviceProvider = serviceProvider; 
    } 

    ...  
} 

그런 다음 당신이 조롱 IServiceProvider을 소개하는 조롱 프레임 워크를 사용할 수있는 연결 방법 예상 된 매개 변수와 함께 호출되었습니다.

실제 서버와 실제 영구 저장 장치를 사용하기 때문에 다른 두 클래스를 테스트하는 것이 더 어렵습니다. 간접 지정 계층을 추가하여 지연 (예 : SettingsProvider에서 사용하는 PersistenceProvider)하지만 결국에는 단위 테스트의 세계를 떠나 통합 테스트를 입력 할 수 있습니다. 일반적으로 위의 패턴으로 코드를 작성할 때 모델 및 뷰 모델은 좋은 단위 테스트 커버리지를 얻을 수 있지만 공급자는보다 복잡한 테스트 방법을 필요로합니다.

물론 EventAggregator를 사용하여 커플 링을 중단하고 테스트를 용이하게하기 위해 IOC를 사용하면 Microsoft Prism과 같은 종속성 주입 프레임 워크 중 하나를 조사 할 가치가 있습니다. 그러나 개발에 너무 늦은 경우에도 - 설계자는 많은 규칙과 패턴을 기존 코드에 더 간단하게 적용 할 수 있습니다.

관련 문제