Sunday, April 26, 2015

Prism vs Caliburn.Micro vs Catel vs MvvmLight

Сегодня я решил написать развернутую статью о популярных на момент написания статьи MVVM фреймворках для работы с WPF. С некоторыми из них я работал очень плотно, с некоторыми − поверхностно, но в целом я думаю я могу составить свою оценку об этих фреймворках, а посмотрев мой анализ вы лучше сможете определится какой из фреймворков использовать для той или иной функциональности. Надеюсь мой обзор вам понравится, и вы определитесь с тем что вам больше по душе, ну и плюс ко всему я постараюсь предоставить как можно больше картинок по каждому из этих фреймворков. Сегодня у нас на повестке для такие фреймворки: Mvvm Light ToolkitCaliburn.MicroPrism и Catel. Все из них довольно популярные и некоторые являются даже целыми платформами как например Catel.
Рассмотрим их по популярности использования. Первым по количеству скачиваний в NuGet Package Manager идет Mvvm Light.
На втором месте идет Prism.
На третьем − Caliburn.Micro.
На четвертом месте идет Catel.
Во-первых, самая главная особенность данных фремворков − в том, что они все работают с поддержкой паттерна MVVM. Ниже на рисунке показаны поддерживаемые платформы и указана последняя актуальная версия библиотеки на момент написания. В соответствующих колонках плюсами отмечено, что данная платформа поддерживает данный фреймворк. Соответственно, минус обозначает, что такая поддержка отсутствует. Во всех рисунках мы будем сортировать наши фреймворки по их популярности (количеству загрузок).
*Я не выделял отдельно Xamarin.iOS, Xamarin.Android и т.д., а написал в общем, чтобы подчеркнуть те фреймворки, которые хотя бы что-то из этого списка покрывают.
Теперь немного детальнее о каждом из данных фреймворков по поддержке платформ.
Mvvm Light – фреймфорк, который занял все ниши, по сути. Не так давно команда GalaSoft анонсировала поддержку Xamarin. Но им пришлось много потрудиться и выкинуть часть логики, которая была раньше, а точнее переписать ее. Изменения затронули, в первую очередь, работу с диалоговыми окнами и навигацией. На данный момент это самый популярный MVVM фреймворк, в первую очередь из-за того, что он достаточно легковесный, и нужно потратить минимум времени, чтобы начать с ними работать.
Prism – это, наверное, единственный фреймворк, который занял свою основную нишу в написании прикладных программ на WPF. В 2014 году команда Patterns&Practices заявила о том, что Silverlight они не будут поддерживать. Актуальна версия с поддержкой Silverlight если я не ошибаюсь, 4.1.0. Под Xamarin использование Prism пытаются допилить разные любители, но пока что это все в очень зародковом состоянии.
Caliburn.Micro – очередной фреймворк для работы с MVVM паттерном, но его основная роль больше прижилась в Windows Phone/RT. Для WPF данный фреймворк тоже используют достаточно часто, но в целом для Windows разработки он менее популярен из-за специфики синтаксиса. Многие разработчики, которые пишут на WPF, не могут быстро привыкнуть к этому. А тем, которые привыкли к классическому MVVM, становится еще сложнее, так как в Caliburn.Micro нет классических команд (ICommand интерфейс) и других своеобразных мелочей. Но тем не менее, он покрывает практически все платформы, которые мы рассмотрели.
Catel.Mvvm – это настоящая целая платформа. Он идет в поставку под лицензией MIT и кроме классической поддержки MVVM паттерна и основных сценариев, в нем есть куча разных контролов, поддержка вариации, разные behaviors и куча других плюшек. Это делает его достаточно монстрообразным. Ниже на рисунке приведён список пространств имен для Catel.Mvvm.
При этом здесь не приведена основная библиотека Catel.Core, которая больше в раза два. Плюсы данного фреймворка − это его возможности, но при этом он намного сложнее, чем все фреймворки, рассмотренные ранее. Но он очень быстро развивается; уже разработан необходимый функционал для покрытия разработки под Xamarin и Universal Apps для Windows Store.
Зависимости фреймфорков
На рисунке ниже приведен весь список зависимостей данных библиотек.
Пройдемся по этим зависимостям. Как мы видим из рисунка выше, больше всех зависимостей у нас имеет Prism. Основная причина этого заключается в том, что Prism − очень модульный проект, и некоторые из библиотек вы можете себе ставить в проект отдельно. То есть, не весь фреймворк целиком, а какую-то его часть. Заметьте также, что Prism − единственный фреймворк из всех, который не добавляет библиотеку в проект System.Windows.Interactivity по той причине, что он может запросто обходиться без нее. В остальных же в той или иной степени есть зависимость от этой библиотеки. 
На втором месте по количеству библиотек идет Mvvm Light, который тянет три свои библиотеки и одну стороннюю CommonServiceLocator
Дальше по зависимости менее всего сторонних библиотек тянут Caliburn.Micro и Catel.
Размер фреймворков
Эта часть поможет, наверное, тем, кому важен размер поставляемых библиотек. В это трудно поверить, но есть такие люди, для которых размер поставляемых библиотек имеет значение. В размер включены все библиотеки, которые приведены на рисунке выше.
Как мы видим, Catel занимает больше всего места, так как логики там написано действительно много. Все замеры проводились для WPF, поскольку это основная платформа, на которой я пишу код.
Теперь более детально остановимся на возможностях, которые покрывают данные фреймворки. Мы будем понемногу останавливаться на каждом и указывать, какие основные сценарии данный фреймворк покрывает, а какие − нет.
View and ViewModel communication
Основное внимание в связывании View и ViewModel в данных фреймворках уделяется в основном Caliburn.Micro, поскольку в нем придерживается подход VMF (View-ModelFirst), в котором первое место отводится представлению. Правда, можно идти и по другому пути, начиная с разработки моделей и моделей представлений, но при использовании Caliburn.Micro рекомендуют использовать именно этот подход. В остальных все намного проще, и можно начинать в принципе из чего угодно, но в основном все начинают с моделей и моделей представлений, и только после этого приступают к самим представлениям. Еще немного неудобный синтаксис для тех, кто привык использовать команды в своих проектах на основе паттерна MVVM. Ниже приведен простой пример использования взаимодействия с интерфейсом пользователя для каждого из рассмотренных фреймворков.
Mvvm Light:
public class MyViewModel
{
    private RelayCommand _pressButtonCommand;
    public RelayCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new RelayCommand(PressButton));
        }
    }

    private void PressButton()
    {
        //TODO Something
    }
}
Выше приведена модель представления для Mvvm Light с использованием интерфейса ICommand, который представлен классами RelayCommand и RelayCommand<T>.
Prism:
public class MyViewModel
{
    private DelegateCommand _pressButtonCommand;
    public DelegateCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new DelegateCommand(PressButton));
        }
    }

    private void PressButton()
    {
        //TODO Something
    }
}
Как видите, использование команд в MvvmLight, Prism и Catel реально похоже. К Catel мы дойдем в последнюю очередь.
Caliburn.Micro:
Модель представления приведена ниже.
public class ShellViewModel : PropertyChangedBase
{
    public void ShowFirstName()
    {
        MessageBox.Show("Hello World");
    }
}
Более интересный байндинг этого в XAML.
<Button Content="Show Message" VerticalAlignment="Center" HorizontalAlignment="Center"
    cal:Message.Attach="[Event Click] = [Action ShowFirstName()]"/>
Как видим, все предельно просто. Я привел один из вариантов записи байдинга. Всего их существует несколько вариантов. Нужно просто немного к этому привыкнуть.
Catel.Mvvm:
public class MyViewModel
{
    private Command _pressButtonCommand;
    public Command PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new Command(PressButton));
        }
    }

    private void PressButton()
    {
        //TODO Something
    }

}
Все рассмотренные фреймворки в этом плане довольно стандартны в плане использования, за исключением Caliburn.Micro. К его синтаксису придется привыкать некоторое время.
Использование EventAggregator
Здесь начинается самое интересное. Мы рассмотрим, как каждый из наших фреймворков реализует использование данного паттерна. В Catel.Mvvm нет EventAggregator, но это не значит, что данный фреймворк не может организовать обмен сообщениями. Он это делает с помощью реализованного паттерна Mediator (паттерн посредник). Ну что же, давайте рассмотрим простенькие примеры, чтобы понять, как используется та или иная возможность в каждом из этих фреймворков.
Mvvm Light:
public class MyViewModel
{
    public MyViewModel()
    {
        Messenger.Default.Register<Comment>(
            this,
            ReceiveMessage);
    }

    private RelayCommand _pressButtonCommand;
    public RelayCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new RelayCommand(PressButton));
        }
    }

    private void PressButton()
    {
        Messenger.Default.Send(new Comment
        {
            Name = "Hello World"
        });
    }

    public void ReceiveMessage(Comment comment)
    {
        MessageBox.Show(comment.Name);
    }
}

public class Comment
{
    public string Name { getset; }
}
Mvvm Light предоставляет нам одну из самых простых реализаций EventAggregator, по сравнению с другими MVVM фреймворками. Эти изменения я добавил в статью после дельного замечания от Дмитрия Бондаря о том, что в Mvvm Light присутствует паттерн EventAggregator в виде класса Messenger. Теперь вы сами можете увидеть, что все рассмотренные фреймворки в той или иной степени реализуют данный паттерн.
Prism:
public class MyEvent : PubSubEvent<string> { }

public class MyViewModel
{
    private readonly IEventAggregator _eventAggregator = new EventAggregator();

    public MyViewModel()
    {
        _eventAggregator.GetEvent<MyEvent>().Subscribe(GetMessage);
    }

    private void GetMessage(string text)
    {
        MessageBox.Show(text);
    }

    private DelegateCommand _pressButtonCommand;
    public DelegateCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new DelegateCommand(PressButton));
        }
    }

    private void PressButton()
    {
        _eventAggregator.GetEvent<MyEvent>().Publish("Hello World");
    }
}
Выше представлен рабочий пример с использованием EventAggregator в Prism. Вместо полных примеров для статьи, посмотрим на общую концепцию и идею использования.
Caliburn.Micro:
public class ShellViewModel : PropertyChangedBaseIHandle<string>
{
    private readonly IEventAggregator _eventAggregator = new EventAggregator();
        
    public ShellViewModel()
    {
        _eventAggregator.Subscribe(this);
    }

    public void ShowFirstName()
    {
        _eventAggregator.PublishOnUIThread("Hello World");
    }

    public void Handle(string message)
    {
        MessageBox.Show(message);
    }
}
Мне больше по душе запись, которая в данном фреймворке чем та, которая сделана в Prism. Хотя в целом, как вы сами видите, имплементировать использование EventAggregator достаточно просто.
Catel.Mvvm
А вот в Catel логика отличается. Здесь она построена вокруг паттерна Mediator (посредник), задачей которого является взаимодействие множества объектов без необходимости явно ссылаться друг на друга. Паттерн EventAggregator является более сложной версияей паттерна Observer (наблюдатель). По сути, вы используете два разных подхода, при этом вы решаете одну и ту же задачу, просто разными методами.
public class MyViewModel
{
    private IMessageMediator _messageMediator = MessageMediator.Default;

    public MyViewModel()
    {
        MessageMediatorHelper.SubscribeRecipient(this);
    }

    private Command _pressButtonCommand;
    public Command PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new Command(PressButton));
        }
    }

    private void PressButton()
    {
        _messageMediator.SendMessage("Hello World""New Message");
    }

    [MessageRecipient(Tag = "New Message")]
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }

}
Я не добавлял логики, где участвуют много моделей, а показал все на синтаксических примерах, когда одна и та же модель представления выступает в роли как подписчика, так и получателя.
Использование навигации (Navigation)
Теперь пройдемся по навигации в данных фреймворках. Для этой темы я решил не писать код, поскольку он выйдет достаточно большим. Возможно, добавлю только синтаксические примеры, чтобы можно было увидеть, как это работает. Начнем, пожалуй, разбор полетов этих фреймворков по их функциональных возможностях навигации, начиная от самых простых и заканчивая самыми продвинутыми в данном плане. На первом месте по самой простой реализации и использовании (проще уже некуда, поскольку ничего, по сути, толком не реализовано) идет MvvmLightДля некоторых платформ MvvmLight предоставляет классы для реализации навигации, как, например, для Windows Phone, но для WPF он предоставляет пустой интерфейс, который нужно реализовывать самому.
public interface INavigationService
{
    string CurrentPageKey { get; }

    void GoBack();
       
    void NavigateTo(string pageKey);
        
    void NavigateTo(string pageKey, object parameter);
}
Всю необходимую логику вам нужно будет добавлять самостоятельно. Пример такой логики приведен ниже.
public class MyViewModel
{
    public INavigationService _navigationService;

    public MyViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    private RelayCommand _pressButtonCommand;
    public RelayCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new RelayCommand(PressButton));
        }
    }

    private void PressButton()
    {
        _navigationService.NavigateTo("Details""Hello World");
    }
}
Пример чисто схематический, правда, рабочий для Windows 8, Xamarin Android/iOS и для Windows Phone, начиная с 8-ой версии.
Навигация − сама по себе непростая штука, так что, как мы видим, MvvmLight в этом плане больше ушел в сторону разработки для мобильных платформ.
Caliburn.Micro
Caliburn.Micro тоже, к сожалению, имеет только интерфейс для навигации в Windows Phone 8 и выше, а также для Universal Apps. Не знаю, лучше выходит это или хуже того, что имеем в MvvmLight, но использование что одного, что второго ограничивается только мобильными приложениями. Использование очень похоже, как в примере для MvvmLight.
Catel.Mvvm
В этом плане Catel просто "переплевывает" двух предыдущих конкурентов тем, что он предоставляет сервис не только для WPF, но и охватывает свою часть для мобильных платформ.
public class MyViewModel
{
    private readonly INavigationService _navigationService;

    public MyViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    private Command _pressButtonCommand;
    public Command PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new Command(PressButton));
        }
    }

    private void PressButton()
    {
        _navigationService.Navigate(new Uri("InboxView"UriKind.Relative));
    }
}
Catel работает поверх навигации, встроенной в ту среду, в которой вы пишете. Если это WPF, то это соответственные сервисы по навигации над ним и т.д.
Prism
В плане навигации Prism − самый крутой и продвинутый. Например, ниже приведен самый простой пример навигации в Prism.
public class MyViewModel
{
    private readonly IRegionManager _regionManager;

    public MyViewModel(IRegionManager regionManager)
    {
        _regionManager = regionManager;
    }

    private DelegateCommand _pressButtonCommand;
    public DelegateCommand PressButtonCommand
    {
        get
        {
            return _pressButtonCommand ?? (_pressButtonCommand = new DelegateCommand(PressButton));
        }
    }

    private void PressButton()
    {
        _regionManager.RequestNavigate("MainRegion"new Uri("InboxView"UriKind.Relative));
    }
}
Сразу пройдемся по отличиям. Первое основное отличие заключается в том, что навигация в Prism не работает поверх встроенной навигации в WPF. Логика навигации в Prism построенная поверх работы с регионами, основной единицы модульности в Prism. Навигация в Prism сделана так, что она позволяет управлять поведением в рамках одного представления с очисткой представлений, если это необходимо. Это отдельная тема, которую вы можете почитать у меня в блоге. В-третьих, навигация в призме бывает двух видов: навигация на основании представлений (работает поверх представлений) и навигация на основе состояний (state-based navigation).
Работа с диалоговыми окнами
Последний пункт, на котором хотелось бы заострить внимание, − это работа с диалоговыми окнами. Поскольку паттерн MVVM запрещает работать непосредственно с диалоговыми окнами напрямую с модели представления, приходится реализовывать интерфейс наподобие IDialogService, чтобы спрятать всю бизнес-логику за реализацией этого сервиса.
Такой интерфейс нам предоставляет MvvmLight.
public interface IDialogService
{
    Task ShowError(Exception error, string title, string buttonText, Action afterHideCallback);
       
    Task ShowError(string message, string title, string buttonText, Action afterHideCallback);
        
    Task ShowMessage(string message, string title);
        
    Task ShowMessage(string message, string title, string buttonText, Action afterHideCallback);
        
    Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback);
        
    Task ShowMessageBox(string message, string title);
}
Для Calibur.Micro дела обстоят похуже, так как нужно скачивать отдельно разные вспомогательные хелперы. Для Prism, к сожалению, такую имплементацию также нужно делать вручную. Но большинство сценариев Prism покрывает с помощью уже сделанных внутренних механизмов, о которых вы можете почитать в статье "Использование объектов запроса взаимодействия в Prism 5". Ну и более всех крут в этом плане Catel, который имеет полностью реализованный интерфейс IUIVisualizerService, который реализует всю необходимую логику.


Итоги
В статье мы кратко рассмотрели самые популярные на момент написания WPF фреймворки, описали их область покрытия, частоту использования и возможности. Каждый из фреймворком имеет еще множество не рассмотренных здесь возможностей, которые присущи только ему. В целом, целью было выделить общую логику, которая может вам понадобиться, и показать степень ее покрытия тем или иным фреймворком. Надеюсь, что данная статья поможет вам определиться с выбором хорошего, а главное − такого фреймворка для работы с MVVM паттерном, который будет покрывать все ваши сценарии. Ну или практически все. Буду благодарен за ваши комментарии к статье и пожеланиям, обзор какого MVVM фреймворка вы бы хотели еще увидеть. 

No comments:

Post a Comment