Что такое ViewModelLocator и каковы его плюсы и минусы по сравнению с DataTemplates?

112

Может ли кто-нибудь дать мне краткое описание того, что такое ViewModelLocator, как он работает и каковы плюсы и минусы его использования по сравнению с DataTemplates?

Я попытался найти информацию в Google, но, похоже, существует много различных его реализаций и нет подробного списка того, что это такое, и плюсов / минусов его использования.

Рэйчел
источник

Ответы:

204

вступление

В MVVM обычная практика состоит в том, чтобы представления находили свои модели представления, разрешая их из контейнера внедрения зависимостей (DI). Это происходит автоматически, когда контейнеру предлагается предоставить (разрешить) экземпляр класса View. Контейнер вводит ViewModel в View, вызывая конструктор View, который принимает параметр ViewModel; эта схема называется инверсией управления (IoC).

Преимущества DI

Основное преимущество здесь заключается в том, что контейнер можно настроить во время выполнения с инструкциями о том, как разрешать типы, которые мы запрашиваем у него. Это обеспечивает большую тестируемость, давая ему указание разрешать типы (представления и модели представления), которые мы используем, когда наше приложение действительно работает, но инструктируя его по-другому при запуске модульных тестов для приложения. В последнем случае у приложения даже не будет пользовательского интерфейса (он не работает; есть только тесты), поэтому контейнер будет разрешать фиктивные типы вместо «нормальных» типов, используемых при запуске приложения.

Проблемы, проистекающие из DI

До сих пор мы видели, что подход DI позволяет легко тестировать приложение, добавляя уровень абстракции над созданием компонентов приложения. У этого подхода есть одна проблема: он плохо работает с визуальными дизайнерами, такими как Microsoft Expression Blend.

Проблема в том, что как при обычном запуске приложения, так и при запуске модульного теста кто-то должен настроить контейнер с инструкциями о том, какие типы разрешать; кроме того, кто-то должен попросить контейнер разрешить представления, чтобы в них можно было внедрить модели представления.

Однако во время разработки нашего кода нет . Дизайнер пытается использовать отражение для создания экземпляров наших представлений, что означает, что:

  • Если конструктору View требуется экземпляр ViewModel, дизайнер вообще не сможет создать экземпляр View - он каким-то контролируемым образом выдаст ошибку.
  • Если View имеет конструктор без параметров, View будет создан, но так и DataContextбудет, nullпоэтому мы получим «пустое» представление в дизайнере, что не очень полезно.

Введите ViewModelLocator

ViewModelLocator - это дополнительная абстракция, используемая следующим образом:

  • Сам View создает экземпляр ViewModelLocator как часть своих ресурсов и привязывает свой DataContext к свойству ViewModel локатора.
  • Локатор каким-то образом определяет, находимся ли мы в режиме разработки
  • Если не в режиме разработки, локатор возвращает ViewModel, который он разрешает из контейнера DI, как описано выше.
  • В режиме разработки локатор возвращает фиксированную «фиктивную» ViewModel, используя свою собственную логику (помните: во время разработки нет контейнера!); эта ViewModel обычно предварительно заполнена фиктивными данными

Конечно, это означает, что View должен иметь конструктор без параметров для начала (иначе дизайнер не сможет создать его экземпляр).

Резюме

ViewModelLocator - это идиома, которая позволяет вам сохранить преимущества DI в вашем приложении MVVM, а также позволяет вашему коду хорошо взаимодействовать с визуальными дизайнерами. Иногда это называется «смешиваемость» вашего приложения (имеется в виду Expression Blend).

После переваривания выше, см практического примера здесь .

Наконец, использование шаблонов данных - это не альтернатива использованию ViewModelLocator, а альтернатива использованию явных пар View / ViewModel для частей вашего пользовательского интерфейса. Часто вы можете обнаружить, что нет необходимости определять View для ViewModel, потому что вместо этого вы можете использовать шаблон данных.

Джон
источник
4
+1 за отличное объяснение. Можете ли вы подробнее рассказать о представлении и его ресурсах? Под Ресурсами вы имеете в виду свойства представления? Или? У вас есть ссылка на конкретный пример этого паттерна?
Metro Smurf
@MetroSmurf: Ваша ссылка находится в разделе «Сводка».
Джон
1
Спасибо. Есть ли ограничения на использование ViewModelLocator? У меня были некоторые опасения по поводу того факта, что он ссылается на статический ресурс - можно ли создавать модели ViewModels динамически во время выполнения? И нужно ли много лишнего кода для подключения?
Рэйчел
@Rachel: Эта же ссылка в Резюме должна ответить на эти вопросы с практическими примерами.
Джон
2
В высшей степени обманчивый ответ. Основная цель View Model Locator - не предоставлять конструктору фиктивные данные. Вы можете легко сделать это, указав d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}". Цель локатора - фактически включить DI в представлениях, потому что WPF плохо его предоставляет. Пример: у вас есть главное окно, которое открывает какое-то диалоговое окно. Чтобы решить DI в диалоговом окне обычным способом, вам нужно будет передать его как зависимость от главного окна! Этого можно избежать с помощью локатора просмотра.
hyankov
10

Пример реализации ответа @Jon

У меня есть класс локатора модели просмотра. Каждое свойство будет экземпляром модели представления, которую я собираюсь разместить в своем представлении. Я могу проверить, работает ли код в режиме разработки или не используется DesignerProperties.GetIsInDesignMode. Это позволяет мне использовать фиктивную модель во время проектирования и реальный объект, когда я запускаю приложение.

public class ViewModelLocator
{
    private DependencyObject dummy = new DependencyObject();

    public IMainViewModel MainViewModel
    {
        get
        {
            if (IsInDesignMode())
            {
                return new MockMainViewModel();
            }

            return MyIoC.Container.GetExportedValue<IMainViewModel>();
        }
    }

    // returns true if editing .xaml file in VS for example
    private bool IsInDesignMode()
    {
        return DesignerProperties.GetIsInDesignMode(dummy);
    }
}

А чтобы использовать его, я могу добавить свой локатор в App.xamlресурсы:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

<Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>

А затем подключить ваше представление (например, MainView.xaml) к вашей модели просмотра:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">
BrunoLM
источник
есть ли разница в использовании thisвместо dummy?
Себастьян Хавери Вишневецкий,
5

Я не понимаю, почему другие ответы на этот вопрос обтекают Дизайнера.

Назначение локатора модели представления - позволить вашему представлению создать экземпляр этого (да, локатор модели представления = сначала просмотр):

public void MyWindowViewModel(IService someService)
{
}

вместо этого:

public void MyWindowViewModel()
{
}

заявив об этом:

DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

Где ViewModelLocatorкласс, который ссылается на IoC и как он решает MainWindowModelсвойство, которое он предоставляет.

Это не имеет ничего общего с предоставлением модели представления для вашего представления. Если хочешь этого, просто сделай

d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"

Локатор модели представления - это оболочка для некоторого (любого) контейнера Inversion of Control, такого как, например, Unity.

Ссылаться на:

Хьянков
источник
Локатор модели представления не требует контейнера, пользователь решает, как модель представления разрешается через конфигурацию, и вы можете использовать контейнер или просто создать новый тип самостоятельно. Таким образом, вы можете определять местоположение модели представления на основе соглашения, например, вместо предварительной регистрации всех ваших представлений и моделей представлений в каком-либо контейнере.
Крис Бордеман,
Вы правы, когда говорите: « Я не понимаю, почему другие ответы [...] обтекают Дизайнер » +1, но цель локатора состоит в том, чтобы убрать из представления любое знание о том, как выглядит модель представления. created, что делает представление независимым от этого экземпляра, оставляя это на усмотрение локатора. Локатор сможет предоставлять различные варианты модели представления, возможно, некоторые пользовательские, добавленные через плагины, которыми локатор будет управлять (и конкретный для времени разработки). Представление будет очищено от любого из этого процесса поиска правильной версии модели представления, что действительно хорошо для SoC.
мин