Если ViewModel инициализируется через конструктор, свойства или вызов метода

Я борюсь с несколькими различными концепциями дизайна в контексте MVVM, которые в основном связаны с вопросом о том, когда инициализировать ViewModel. Чтобы быть более конкретным с точки зрения "инициализации", я имею в виду загрузку значений, таких как значения выбора, контекст безопасности и другие вещи, которые в некоторых случаях могут вызывать несколько секунд задержки.

Возможные стратегии:

  • Передавать аргументы конструктору ViewModel и выполнять загрузку в конструкторе.
  • Поддерживать конструктор без параметров без изменений в ViewModel и вместо этого поддерживать инициализацию методов, которые принимают параметры и выполняют загрузку.
  • Комбинация опций 1 и 2, где аргументы передаются конструктору ViewModel, но загрузка отложена до тех пор, пока не будет вызван метод Initialize.
  • Вариант 3, где вместо параметров, передаваемых конструктору ViewModel, они устанавливаются непосредственно в свойствах.

Влияет на getter и seters свойств ViewModel

В случаях, когда инициализация отложена, необходимо знать, находится ли ViewModel в состоянии, которое считается доступным, для которого свойство IsBusy обычно используется так же, как и для других операций async и timeusing. Это также означает, что это связано с тем, что, поскольку большинство свойств в ViewModel выставляют значения, полученные из объекта модели, нам постоянно приходится писать следующий тип сантехники, чтобы убедиться, что модель доступна.

public string Name
{
    get 
    {  
        if (_customerModel == null) // Check model availability
        {
            return string.Empty;
        }

        _customerModel.Name;
    }
}

Хотя проверка проста, она просто добавляет к сантехнике INPC и другим типам предметов первой необходимости, которые заставляют ViewModel стать несколько громоздкой для написания и поддержки. В некоторых случаях это становится еще более проблематичным, поскольку не всегда может быть разумным по умолчанию возврат из свойства getter, например, с логическим свойством IsCommercialAccount, если, если нет доступной модели, нет смысла возвращать true или ложный ставит под сомнение целый ряд других вопросов дизайна, таких как неопределенность. В случае варианта 1 сверху, где мы передали все в конструктор и загрузили его, нам нужно только заняться NULL ViewModel из представления и когда ViewModel не является нулевым, он будет гарантированно инициализирован.

Поддержка отложенной инициализации

С опцией 4 также можно полагаться на ISupportInitialize, который может быть реализован в базовом классе ViewModel для обеспечения согласованного способ сигнализации о том, инициализирован ли ViewModel или нет, а также для запуска инициализации с помощью стандартного метода BeginInit. Это также можно использовать в случае вариантов 2 и 3, но имеет меньшее значение, если все параметры инициализации установлены в одной атомной транзакции. По крайней мере таким образом, приведенное выше условие может превратиться во что-то вроде

Как дизайн влияет на IoC

В терминах IoC я понимаю, что варианты 1 и 3 могут быть выполнены с использованием инжекции конструктора, что обычно является предпочтительным, и что варианты 2 и 4 могут быть выполнены с использованием метода и вставки свойств соответственно. Однако моя забота заключается не в IoC, а в том, как передавать эти параметры, а скорее в общем дизайне и как это влияет на реализацию ViewModel и на публичный интерфейс, хотя я хотел бы быть хорошим гражданином, чтобы сделать IoC немного легче, если это необходимо в будущее.

Тестируемость

Все три варианта, похоже, одинаково поддерживают концепцию тестируемости, что мало помогает в решении этих вариантов, хотя можно утверждать, что для варианта 4 может потребоваться более широкий набор тестов для обеспечения правильного поведения свойств, когда это поведение зависит от состояние инициализации.

Командная способность

Варианты 2, 3 и 4 имеют побочный эффект, связанный с необходимостью кода в методах View для вызова метода ViewModel, но при необходимости они могут отображаться как команды. В большинстве случаев, вероятно, можно было бы загружать вызовы этих методов непосредственно после построения так или иначе, как показано ниже.

var viewModel = new MyViewModel();
this.DataContext = viewModel;
// Wrap in an async call if necessary
Task.Factory.StartNew(() => viewModel.InitializeWithAccountNumber(accountNumber));

Некоторые другие мысли

Я пробовал варианты этих стратегий, поскольку я работал с шаблоном проектирования MVVM, но пока не пришел к лучшему. Я хотел бы услышать, что сообщество думает и пытается найти разумный консенсус относительно наилучшего пути для инициализации ViewModels или иным образом иметь дело с их свойствами, когда они находятся в недоступном состоянии.

Идеальный случай может заключаться в использовании шаблона State, где сам объект ViewModel заменяется разными объектами ViewModel, которые представляют разные состояния. Таким образом, мы могли бы иметь общую реализацию BusyViewModel, которая представляет состояние занятости, которое удаляет одну из потребностей свойства IsBusy в ViewModel, а затем, когда следующая ViewModel готова, она выгружается в представлении, позволяя ViewModel следовать за указанной категорией в варианте 1, где он полностью инициализируется во время строительства. Это оставляет некоторые вопросы о том, кто отвечает за управление переходами состояний, например, BusyViewModel может абстрагировать что-то похожее на BackgroundWorker или Задачу, которая сама выполняет инициализацию, и будет представлять внутреннюю ViewModel, когда она будет готова. С другой стороны, обмен данными DataContext на представлении может потребовать либо обработки события в представлении, либо предоставления ограниченного доступа к свойству DataContext объекта View to BusyViewModel, чтобы его можно было установить в традиционном смысле шаблона состояния. Если есть что-то подобное, что люди делают по этим строкам, я определенно хотел бы знать, потому что мой поиск в Google еще не появился.

Ответ 1

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