Как сделать создание моделей во время выполнения менее болезненным

17

Я прошу прощения за длинный вопрос, это звучит немного как напыщенная речь, но я обещаю, что это не так! Я кратко изложил свой вопрос (ы) ниже

В мире MVC все просто. Модель имеет состояние, представление показывает модель, а контроллер выполняет какие-либо действия с моделью (в основном), контроллер не имеет состояния. Для этого у Контроллера есть некоторые зависимости от веб-сервисов, хранилища, всего. Когда вы создаете экземпляр контроллера, вы заботитесь о предоставлении этих зависимостей, и ничего больше. Когда вы выполняете действие (метод в Controller), вы используете эти зависимости для извлечения или обновления Модели или вызова какой-либо другой доменной службы. Если есть какой-либо контекст, скажем, что какой-то пользователь хочет видеть детали определенного элемента, вы передаете Id этого элемента в качестве параметра для Action. Нигде в контроллере нет ни одной ссылки на какое-либо состояние. Все идет нормально.

Введите MVVM. Я люблю WPF, я люблю привязку данных. Я люблю фреймворки, которые делают привязку данных к ViewModels еще проще (с использованием Caliburn Micro atm). Я чувствую, что в этом мире все не так просто. Давайте снова выполнить упражнение: модель имеет состояние, вид показывает модель представления, и ViewModel делает материал к / с моделью ( в основном), модель представление действительно есть состояние! (уточнить, может быть , он делегирует все свойства в одной или нескольких моделей, но это означает , что он должен иметь ссылку на одну сторону модели или другой, что государство само по себе) Для того, чтобы сделатьВещи ViewModel имеют некоторые зависимости от веб-сервисов, репозитория, лота. Когда вы создаете экземпляр ViewModel, вы заботитесь о предоставлении этих зависимостей, а также о состоянии. И это, дамы и господа, раздражает меня до бесконечности.

Всякий раз, когда вам нужно создать экземпляр ProductDetailsViewModelиз ProductSearchViewModel(из которого вы позвонили, ProductSearchWebServiceкоторый в свою очередь вернулся IEnumerable<ProductDTO>, все еще со мной?), Вы можете сделать одну из следующих вещей:

  • позвоните new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);, это плохо, представьте себе еще 3 зависимости, это означает, что ProductSearchViewModelнеобходимо учитывать и эти зависимости. Также изменение конструктора является болезненным.
  • Вызовите _myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);, фабрика просто Func, они легко генерируются большинством IoC-фреймворков. Я думаю, что это плохо, потому что методы Init - это утечка абстракции. Вы также не можете использовать ключевое слово readonly для полей, которые установлены в методе Init. Я уверен, что есть еще несколько причин.
  • вызов _myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);Итак ... это шаблон (абстрактная фабрика) , которая, как правило , рекомендуется для такого рода проблемы. Я думал, что это был гений, так как он удовлетворял мою тягу к статической типизации, пока я на самом деле не начал использовать его. Объем стандартного кода, я думаю, слишком велик (вы знаете, помимо нелепых имен переменных, которые я использую). Для каждой модели ViewModel, для которой требуются параметры времени выполнения, вы получите два дополнительных файла (заводской интерфейс и реализацию), и вам нужно будет ввести зависимости без времени выполнения, например, 4 дополнительных раза. И каждый раз, когда меняются зависимости, вы можете изменить их и на фабрике. Такое ощущение, что я даже больше не использую DI-контейнер. (Я думаю, что у Castle Windsor есть какое-то решение для этого [со своими собственными недостатками, поправьте меня, если я ошибаюсь]).
  • сделать что-нибудь с анонимными типами или словарем. Мне нравится моя статическая типизация.

Так что да. Смешивание состояния и поведения таким образом создает проблему, которой вообще нет в MVC. И я чувствую, что в настоящее время нет действительно адекватного решения этой проблемы. Теперь я хотел бы наблюдать некоторые вещи:

  • Люди на самом деле используют MVVM. Таким образом, они либо не заботятся обо всем вышеперечисленном, либо у них есть какое-то блестящее другое решение.
  • Я не нашел подробного примера MVVM с WPF. Например, пример проекта NDDD очень помог мне понять некоторые концепции DDD. Мне бы очень понравилось, если бы кто-то мог указать мне в направлении чего-то похожего для MVVM / WPF.
  • Может быть, я неправильно делаю MVVM, и я должен перевернуть свой дизайн с ног на голову. Может быть, у меня не должно быть этой проблемы вообще. Ну, я знаю, что другие люди задавали тот же вопрос, поэтому я думаю, что я не единственный.

Подвести итоги

  • Правильно ли я пришел к выводу, что наличие ViewModel в качестве точки интеграции как состояния, так и поведения является причиной некоторых трудностей с шаблоном MVVM в целом?
  • Является ли использование шаблона абстрактной фабрики единственным / лучшим способом создания экземпляра ViewModel статически типизированным способом?
  • Есть ли что-то похожее на детальную справочную реализацию?
  • Имеет ли много моделей ViewModel с состоянием / поведением запах дизайна?
dvdvorle
источник
10
Это слишком долго, чтобы прочитать, подумайте о пересмотре, там много ненужных вещей. Вы можете пропустить хорошие ответы, потому что люди не будут читать все это.
Яннис
Вы сказали, что любите Caliburn.Micro, но не знаете, как эта платформа может помочь в создании новых моделей представлений? Проверьте некоторые примеры этого.
Эйфорическое
@Euphoric Не могли бы вы быть более конкретным, Google, похоже, не помогает мне здесь. У меня есть ключевые слова, которые я могу найти?
dvdvorle
3
Я думаю, что вы немного упрощаете MVC. Конечно, представление показывает модель в начале, но во время работы она меняет состояние. Это меняющееся состояние, на мой взгляд, является «Редактировать модель». Это упрощенная версия Модели с уменьшенными ограничениями согласованности. Фактически, то, что я называю Редактировать модель - это MVVM ViewModel. Он удерживает состояние во время перехода, которое ранее поддерживалось либо представлением в MVC, либо было перенесено обратно в незафиксированную версию модели, где я не думаю, что она принадлежит. Таким образом, у вас было состояние "в движении" раньше. Теперь все это в ViewModel.
Скотт Уитлок
@ ScottWhitlock Я действительно упрощаю MVC. Но я не говорю, что это неправильно, что состояние "в потоке" находится в ViewModel, я говорю, что переполнение поведения там также затрудняет инициализацию ViewModel в пригодное для использования состояние, скажем, из другой ViewModel. Ваша «Редактировать модель» в MVC не знает, как сохранить себя (у нее нет метода Save). Но контроллер знает это и имеет все зависимости, необходимые для этого.
dvdvorle

Ответы:

2

Проблема зависимостей при запуске новой модели представления может быть решена с помощью IOC.

public class MyCustomViewModel{
  private readonly IShoppingCartWebService _cartService;

  private readonly ITimeService _timeService;

  public ProductDTO ProductDTO { get; set; }

  public ProductDetailsViewModel(IShoppingCartWebService cartService, ITimeService timeService){
    _cartService = cartService;
    _timeService = timeService;
  }
}

При настройке контейнера ...

Container.Register<IShoppingCartWebService,ShoppingCartWebSerivce>().As.Singleton();
Container.Register<ITimeService,TimeService>().As.Singleton();
Container.Register<ProductDetailsViewModel>();

Когда вам нужна модель вашего вида:

var viewmodel = Container.Resolve<ProductDetailsViewModel>();
viewmodel.ProductDTO = myProductDTO;

При использовании каркаса, такого как caliburn micro, часто уже присутствует та или иная форма контейнера МОК.

SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);
Майк
источник
1

Я ежедневно работаю с ASP.NET MVC и работаю над WPF более года, и вот как я это вижу:

MVC

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

Представление отвечает за отображение модели.

Модель, как правило, включает в себя данные (например, UserId, FirstName), а также состояние (например, заголовки) и, как правило, зависит от конкретного вида.

MVVM

Модель обычно содержит только данные (например, UserId, FirstName) и обычно передается

Модель представления охватывает поведение представления (методы), его данные (модель) и взаимодействия (команды) - аналогично активному шаблону MVP, когда докладчик осведомлен о модели. Модель вида специфична для вида (1 вид = 1 вид модели).

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


Следует помнить, что шаблон представления MVVM специфичен для WPF / Silverlight из-за их привязки к данным.

Представление, как правило, знает, с какой моделью представления оно связано (или его абстракция).

Я бы посоветовал вам рассматривать модель представления как одноэлементную, даже если она создается для каждого представления. Другими словами, вы должны быть в состоянии создать его через DI через контейнер IOC и вызвать соответствующие методы, чтобы сказать; загрузить его модель на основе параметров. Что-то вроде этого:

public partial class EditUserView
{
    public EditUserView(IContainer container, int userId) : this() {
        var viewModel = container.Resolve<EditUserViewModel>();
        viewModel.LoadModel(userId);
        DataContext = viewModel;
    }
}

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

Shelakel
источник
Если мое FirstName - «Питер», а мои названия - {«Rev», «Dr»} *, почему вы учитываете данные FirstName и состояние заголовка? Или вы можете уточнить свой пример? * не совсем
Пит Киркхам
@PeteKirkham - пример «заголовков», на который я ссылался, например, в поле со списком. Как правило, когда вы отправляете информацию для сохранения, вы не будете отправлять состояние (например, список штатов / провинций / названий), из которого были сделаны выборы. Любое стоящее состояние для передачи с данными (например, используемое имя пользователя) следует проверять в точке обработки, поскольку состояние могло устареть (если вы использовали какой-то асинхронный шаблон, такой как организация очереди сообщений).
Shelakel
Несмотря на то, что с момента публикации этой статьи прошло два года, я должен сделать комментарий в пользу будущих зрителей: ваш ответ беспокоил меня две вещи. View может соответствовать одной ViewModel, но ViewModel может быть представлен несколькими View. Во-вторых, то, что вы описываете, - это анти-паттерн Service Locator. ИМХО, вы не должны напрямую разрешать видовые модели везде. Для этого и нужен DI. Сделайте свое решение в меньшее количество пунктов, как вы можете. Пусть Caliburn сделает эту работу за вас, например.
Джони Адамит
1

Краткий ответ на ваши вопросы:

  1. Да, состояние + поведение приводит к этим проблемам, но это верно для всех ОО. Настоящим виновником является соединение ViewModels, что является своего рода нарушением SRP.
  2. Статически напечатано, наверное. Но вы должны уменьшить / исключить необходимость создания экземпляров ViewModels из других ViewModels.
  3. Не то чтобы я был в курсе.
  4. Нет, но наличие ViewModel с несвязанным состоянием и поведением (как некоторые ссылки на модель и некоторые ссылки на ViewModel)

Длинная версия:

Мы столкнулись с той же проблемой, и нашли некоторые вещи, которые могут вам помочь. Хотя я не знаю «волшебного» решения, эти вещи немного облегчают боль.

  1. Реализуйте привязываемые модели из DTO для отслеживания и проверки изменений. Эти «Data» -ViewModels не должны зависеть от сервисов и не приходят из контейнера. Они могут быть просто «новы» отредактированы, переданы и даже могут быть выведены из DTO. Суть в том, чтобы реализовать модель, специфичную для вашего приложения (например, MVC).

  2. Развяжите ваши ViewModels. Caliburn позволяет легко соединить ViewModels вместе. Он даже предлагает это через свою модель экрана / проводника. Но эта связь делает ViewModels трудными для модульного тестирования, создает много зависимостей и самое важное: накладывает бремя управления жизненным циклом ViewModel на ваши ViewModels. Один из способов развязать их - использовать что-то вроде службы навигации или контроллера ViewModel. Например

    открытый интерфейс IShowViewModels {void Show (объект inlineArgumentsAsAnonymousType, строка regionId); }

Еще лучше сделать это с помощью какой-либо формы обмена сообщениями. Но важно не обрабатывать жизненный цикл ViewModel из других моделей ViewModel. В MVC контроллеры не зависят друг от друга, а в MVVM ViewModels не должны зависеть друг от друга. Интегрируйте их другими способами.

  1. Используйте ваши контейнеры типа "stringly" / динамические функции. Хотя возможно создание чего-то похожего INeedData<T1,T2,...>и принудительное применение параметров создания безопасных типов, оно того не стоит. Также не стоит создавать фабрики для каждого типа ViewModel. Большинство контейнеров IoC предоставляют решения для этого. Вы получите ошибки во время выполнения, но разъединение и тестируемость устройства того стоят. Вы все еще проводите какой-то интеграционный тест, и эти ошибки легко обнаруживаются.
sanosdole
источник
0

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

private void RegisterResources()
{
    Container.RegisterType<IDataService, DataService>();
    Container.RegisterType<IProductSearchViewModel, ProductSearchViewModel>();
    Container.RegisterType<IProductDetailsViewModel, ProductDetailsViewModel>();
}

И, учитывая ваши примеры классов, они будут реализованы следующим образом, с контейнером, проходящим через весь путь. Таким образом, любые новые зависимости могут быть легко добавлены, поскольку у вас уже есть доступ к контейнеру.

/// <summary>
/// IDataService Interface
/// </summary>
public interface IDataService
{
    DataTable GetSomeData();
}

public class DataService : IDataService
{
    public DataTable GetSomeData()
    {
        MessageBox.Show("This is a call to the GetSomeData() method.");

        var someData = new DataTable("SomeData");
        return someData;
    }
}

public interface IProductSearchViewModel
{
}

public class ProductSearchViewModel : IProductSearchViewModel
{
    private readonly IUnityContainer _container;

    /// <summary>
    /// This will get resolved if it's been added to the container.
    /// Or alternately you could use constructor resolution. 
    /// </summary>
    [Dependency]
    public IDataService DataService { get; set; }

    public ProductSearchViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void SearchAndDisplay()
    {
        DataTable results = DataService.GetSomeData();

        var detailsViewModel = _container.Resolve<IProductDetailsViewModel>();
        detailsViewModel.DisplaySomeDataInView(results);

        // Create the view, usually resolve using region manager etc.
        var detailsView = new DetailsView() { DataContext = detailsViewModel };
    }
}

public interface IProductDetailsViewModel
{
    void DisplaySomeDataInView(DataTable dataTable);
}

public class ProductDetailsViewModel : IProductDetailsViewModel
{
    private readonly IUnityContainer _container;

    public ProductDetailsViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void DisplaySomeDataInView(DataTable dataTable)
    {
    }
}

Весьма распространено иметь класс ViewModelBase, из которого получены все ваши модели представлений, который содержит ссылку на контейнер. Если вы привыкли разрешать все модели представлений вместо new()'ingних, это должно значительно упростить разрешение всех зависимостей.

Мартин Купер
источник
0

Иногда лучше перейти к простейшему определению, а не к полноценному примеру: http://en.wikipedia.org/wiki/Model_View_ViewModel, возможно, чтение примера ZK Java более показательно, чем C #.

В другой раз прислушайся к своему внутреннему инстинкту

Имеет ли много моделей ViewModel с состоянием / поведением запах дизайна?

Являются ли ваши модели объектно-табличными сопоставлениями? Возможно, ORM поможет сопоставить объекты домена при обработке бизнеса или обновлении нескольких таблиц.

Джерри Кинг
источник