web-dev-qa-db-ja.com

wpfのプリズムとmvvmライト

MVVMプロジェクトでWPFを開始し、PRISMまたはMVVM Lightを決定する必要があります(これら両方のフレームワークは初めてです)。いくつかの投稿を読みましたが、まだいくつか質問があります。誰かが次の側面にいくつかの光を投げてくださいw.r.t.両方のフレームワーク?:

  1. パフォーマンス:いずれかのフレームワークは、何らかの理由で他のフレームワークよりもパフォーマンスが向上しますか?

  2. アプリ内での通信(viewmodelからviewmodelへ、またはモジュール間など):MVVM LightにはMessenging Serviceがあり、これもかなり簡単に見えるようです。しかし、PRISMには同等のものはないようです。本当? PRISMは相互作用をどのように処理しますか?

  3. 単体テスト:PRISMが単体テストをよりよくサポートしていることを読んでください。 MVVM LightでもNUNITまたはVSTSテストを作成できますか?

28
Padmaja
  1. PrismからMvvmLightにプロジェクトを移動したところ、より高速に動作するようです(非常に主観的)。

  2. PrismとMvvmLightの両方にMediatorが実装されています(PrismのIEventAggregator、MvvmLightのIMessenger)。しかし、IMessengerはIEventAggregatorと比較してより多くの機能(たとえば、トークンを使用したメッセージの送信)を備えており、使用する方がはるかに便利です(次の項目を参照)。

    MvvmLightには、より強力なViewModelBaseクラスもあります。

  3. MvvmLightを使用するアプリケーションは、Prismを使用するアプリケーションよりもテストがはるかに簡単です。たとえば、IMessengerはIEventAggregatorよりもモックが簡単です。

PrismViewModel.cs

using System;
using Microsoft.Practices.Prism.Events;
using Microsoft.Practices.Prism.ViewModel;

// An ugly empty event class
public class StringEvent : CompositePresentationEvent<string> { }

public sealed class PrismViewModel : NotificationObject
{
    private readonly IEventAggregator _eventAggregator;

    private string _name;

    public PrismViewModel(IEventAggregator eventAggregator)
    {
        if (eventAggregator == null)
            throw new ArgumentNullException("eventAggregator");

        _eventAggregator = eventAggregator;
        _eventAggregator.GetEvent<StringEvent>().Subscribe(s => Name = s);
    }

    public string Name
    {
        get { return _name; }
        set
        {
            // boiler-plate code
            if (value == _name) 
                return;
            _name = value;
            RaisePropertyChanged(() => Name);
        }
    }

    public void SendMessage(string message)
    {
        _eventAggregator.GetEvent<StringEvent>().Publish(message);
    }
}

PrismViewModelTestCase.cs

using System;
using FluentAssertions;
using Microsoft.Practices.Prism.Events;
using NSubstitute;
using NUnit.Framework;

public class PrismViewModelTestCase
{
    private static PrismViewModel CreateViewModel(IEventAggregator eventAggregator = null)
    {
        // You can't return Substitute.For<IEventAggregator>()
        // because it returns null when PrismViewModel's constructor
        // invokes GetEvent<StringEvent>() method which leads to NullReferenceException
        return new PrismViewModel(eventAggregator ?? CreateEventAggregatorStub());
    }

    private static IEventAggregator CreateEventAggregatorStub()
    {
        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(Substitute.For<StringEvent>());
        return eventAggregatorStub;
    }

    [Test]
    public void Constructor_WithNonNullEventAggregator_ExpectedSubscribesToStringEvent()
    {
        // Arrange
        var stringEventMock = Substitute.For<StringEvent>();

        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(stringEventMock);

        // Act
        CreateViewModel(eventAggregatorStub);

        // Assert
        // With constrained isolation framework you can only mock virtual members
        // CompositePresentationEvent<TPayload> has only one virtual Subscribe overload with four parameters
        stringEventMock.Received()
                       .Subscribe(Arg.Any<Action<string>>(), Arg.Any<ThreadOption>(), Arg.Any<bool>(),
                                  Arg.Any<Predicate<string>>());
    }

    [Test]
    public void Name_ExpectedRaisesPropertyChanged()
    {
        var sut = CreateViewModel();
        sut.MonitorEvents();

        sut.Name = "any-value";

        sut.ShouldRaisePropertyChangeFor(vm => vm.Name);
    }

    [Test]
    public void SendMessage_ExpectedPublishesStringEventThroughEventAggregator()
    {
        // Arrange
        var stringEventMock = Substitute.For<StringEvent>();

        var eventAggregatorStub = Substitute.For<IEventAggregator>();
        eventAggregatorStub.GetEvent<StringEvent>().Returns(stringEventMock);

        var sut = CreateViewModel(eventAggregatorStub);
        const string expectedPayload = "any-string-payload";

        // Act
        sut.SendMessage(expectedPayload);

        // Assert
        stringEventMock.Received().Publish(expectedPayload);
    }
}

MvvmLightViewModel.cs

using System;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Messaging;

public sealed class MvvmLightViewModel : ViewModelBase
{
    private string _name;

    public MvvmLightViewModel(IMessenger messenger)
    {
        if (messenger == null)
            throw new ArgumentNullException("messenger");

        // ViewModelBase already have field for IMessenger
        MessengerInstance = messenger; 
        MessengerInstance.Register<string>(this, s => Name = s);
    }

    public string Name
    {
        get { return _name; }
        set { Set(() => Name, ref _name, value); // Chic!  }
    }

    public void SendMessage(string message)
    {
        MessengerInstance.Send(message);
    }
}

MvvmLightViewModelTestCase.cs

using System;
using FluentAssertions;
using GalaSoft.MvvmLight.Messaging;
using NSubstitute;
using NUnit.Framework;

public class MvvmLightViewModelTestCase
{
    private static MvvmLightViewModel CreateViewModel(IMessenger messenger = null)
    {
        return new MvvmLightViewModel(messenger ?? Substitute.For<IMessenger>());
    }

    [Test]
    public void Constructor_WithNonNullMessenger_ExpectedRegistersToStringMessage()
    {
        var messengerStub = Substitute.For<IMessenger>();

        var sut = CreateViewModel(messengerStub);

        messengerStub.Received().Register(sut, Arg.Any<Action<string>>());
    }

    [Test]
    public void Name_ExpectedRaisesPropertyChanged()
    {
        var sut = CreateViewModel();
        sut.MonitorEvents();

        sut.Name = "any-value";

        sut.ShouldRaisePropertyChangeFor(vm => vm.Name);
    }

    [Test]
    public void SendMessage_ExpectedSendsStringMessageThroughMessenger()
    {
        var messengerMock = Substitute.For<IMessenger>();
        var sut = CreateViewModel(messengerMock);
        const string expectedMessage = "message";

        sut.SendMessage(expectedMessage);

        messengerMock.Received().Send(expectedMessage);
    }
}

プリズムの短所:

  • 完全にオープンソースではないプロジェクトです(公式のPrismリポジトリは読み取り専用です)
  • もはや積極的に開発されていません
  • クラスを直接使用すると、ボイラープレートで読みにくいコードになります

新しいプロジェクトは、最新のソリューションとアプローチに基づいている必要があると思います。私見、最新のMVVMフレームワーク(Catel、Caliburn.Micro、MvvmLight、ReactiveUIなど)はPrismよりもはるかに優れています。

20
Vladimir Almaev

PrismとMvvmLightを完全に比較することはできません。

PrismはMVVMフレームワークとして知られていますが、Prismはアプリケーションアーキテクチャに関するものです。実際、Prism 5まではMVVMとは関係がなく、Prism 4.1以前ではBaseViewModelクラスがありませんでした。

PrismはMVVMフレームワークではなく、それよりも上位に位置するアプリケーションフレームワークです。 Prism 5ではMVVMのサポートが導入され、Prism 6ではさらにサポートが強化されました。

MVVMは、プリズムが解決するガイダンスを提供する問題の別の側面にすぎません。

Angular vs. Knockoutと比較するようなものです。AngularJSはアプリケーション全体を管理し、アプリケーションコードの構造に関するガイドラインを定義しますが、KnockoutJSではアプリケーション構造は完全にあなた次第です。 PrismとMvvmLightの間の同様のケース。

Prismは、MVVM、依存性注入、コマンド、イベント集約などを含む、適切に構造化され保守可能なXAMLアプリケーションの作成に役立つ設計パターンのコレクションの実装を提供します。 Prismのコア機能は、これらのプラットフォームを対象としたポータブルクラスライブラリの共有コードベースです。 WPF、Windows 10 UWP、およびXamarin Forms。

Wpfを使用してエンタープライズレベルのアプリケーションを開発している場合は、Prismを使用することをお勧めします。

Prismで簡単にできるMVVMに関するこのウェビナーをご覧ください。プレゼンターはブライアンラグナス: https://www.youtube.com/watch?v=ZfBy2nfykqY

MVVM LightやCaliburnなどが「宣伝」されているのと同じ意味で、MSがPRISMを「フレームワーク」として宣伝したことはないと思います。私の理解では、PRISMは常に「実践」として「世界」に提示されていました。これは、単にアプリケーションを構築するための組織化された方法を意味します。 PRISMで提供されるすべてのコードが原因で少し混乱し、アプリケーションの構築に使用できる「フレームワーク」と考えることができます。そして、事実、PRISMに付属のコードを使用して、独自のアプリケーションを構築できます。しかし、PRISMは、私の考えでは、MVVMで使用できる「フレームワーク」よりもはるかに複雑であり、急な学習曲線と「過剰」の可能性があり、アプリケーションを必要以上に複雑にします。アプリケーションの作成時に最新の「フレームワーク」または「練習」を学ぶ時間があれば、それは素晴らしいことです。私の経験では、新しい「練習」や最新の「フレームワーク」を学ぶことを考慮に入れる余裕はありませんが、仕事を成し遂げなければなりません。理想的な世界では、最新かつ最高の「フレームワーク」を使用したり、最新の「プラクティス」を使用したりすることができますが、場合によっては、既に知っていることを実行して完了しなければならないこともあります。

4
John Blacker