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


17

Прошу вибачення за довге запитання, воно читається трохи як шахрайство, але обіцяю, що це не так! Я підсумував мої питання нижче

У світі MVC все зрозуміло. Модель має стан, у Перегляді показана модель, а Контролер виконує роботу з / з Моделлю (в основному), контролер не має стану. Щоб виконати завдання, контролер має певні залежності від веб-служб, сховища, партії. Коли ви призначаєте контролер, ви піклуєтесь про забезпечення цих залежностей, нічого іншого. Виконуючи дію (метод на Controller), ви використовуєте ці залежності для отримання або оновлення Моделі або виклику іншої служби домену. Якщо є якийсь контекст, скажімо, що, якби хтось бажає бачити деталі певного елемента, ви передаєте ідентифікатор цього елемента як параметр дії. Ніде в Контролері немає посилань на будь-який стан. Все йде нормально.

Введіть MVVM. Я люблю WPF, я люблю прив'язку даних. Я люблю рамки, які роблять прив'язку даних до ViewModels ще простішою (використовуючи Micro Atm Caliburn Micro). Я вважаю, що все є менш простим у цьому світі. Давайте робити вправу ще раз: Модель має стан, вид показує модель уявлення, і ViewModel робить матеріал к / с моделлю ( в основному), модель подання дійсно є стан! (Уточнити, може бути , він делегує всі властивості в одній або декількох моделей, але це означає , що він повинен мати посилання на одну сторону моделі або інший, що держава сама по собі) Для того, щоб зробитиречі ViewModel мають деякі залежності від веб-служб, сховища, багато. Коли ви створюєте інсталяцію ViewModel, ви дбаєте про те, щоб забезпечити ці залежності, а також стан. І це, пані та панове, мене дратує без кінця.

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

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

Так, так. Змішування стану та поведінки таким чином створює проблему, яка взагалі не існує в MVC. І я відчуваю, що наразі не існує адекватного рішення цієї проблеми. Тепер я хотів би поспостерігати за деякими речами:

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

Узагальнити

  • Чи правильно я роблю висновок, що те, що ViewModel є точкою інтеграції як стану, так і поведінки, є причиною певних труднощів із схемою MVVM в цілому?
  • Чи використовує абстрактний заводський візерунок єдиний / найкращий спосіб створити ViewModel статично набраним способом?
  • Чи є щось на зразок глибокої довідкової реалізації?
  • Має багато ViewModels із станом та поведінкою дизайнерським запахом?

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

Ви сказали, що любите Caliburn.Micro, але не знаєте, як ця рамка може допомогти створити нові моделі перегляду? Перевірте кілька прикладів цього.
Ейфорія

@Euphoric Ви можете бути дещо більш конкретними, Google, схоже, тут мені не допомагає. Є якісь ключові слова, які я міг шукати?
dvdvorle

3
Я думаю, ти трохи спростиш MVC. Впевнений вигляд показує Модель на початку, але під час роботи вона змінюється. Цей мінливий стан, на мою думку, є "Редагувати модель". Тобто сплющена версія Моделі зі зниженими обмеженнями узгодженості. Насправді те, що я називаю модель редагування - це MVVM ViewModel. Він утримує стан у перехідному періоді, який раніше переглядався або Переглядом у MVC, або відсунутим у невідомій версії Моделі, де я не думаю, що вона належить. Тож у вас раніше було стан "в потоці". Тепер це все у ViewModel.
Скотт Вітлок

@ScottWhitlock Я справді спрощую MVC. Але я не кажу, що це неправильно, що стан "в потоці" знаходиться у ViewModel, я кажу, що зуміння поведінки в ньому також ускладнює ініціалізацію ViewModel до зручного стану, скажімо, з іншого ViewModel. Ваша "Редагувати модель" в MVC не знає, як зберегти себе (у ній немає способу збереження). Але контролер це знає, і має всі залежності, необхідні для цього.
dvdvorle

Відповіді:


2

Питання залежності при ініціації нової моделі перегляду може вирішуватися за допомогою МОК.

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;

При використанні рамки, такої як каліброва мікро , часто є якась форма контейнера МОК.

SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);

1

Я щодня працюю з ASP.NET MVC і працюю над WPF більше року, і ось як я це бачу:

MVC

Контролер повинен оркеструвати дії (отримати це, додати це).

Вид відповідає за показ моделі.

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

МВВМ

Зазвичай модель містить лише дані (наприклад, 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;
    }
}

Як приклад у цьому випадку, ви не створювали б модель перегляду, характерну для користувача, який оновлюється, - натомість модель міститиме конкретні користувацькі дані, які завантажуються через деякий виклик у моделі перегляду.


Якщо моє перше ім’я - "Пітер", а мої заголовки - "Rev", "Dr"} *, чому ви вважаєте дані FirstName та стан заголовка? Або ви можете уточнити свій приклад? * не дуже
Піт Кіркхем

@PeteKirkham - приклад "заголовків", про який я згадував у контексті скажімо, комбобокс. Як правило, коли ви надсилаєте інформацію, яка зберігається, ви не надсилатимете штат (наприклад, список штатів / провінцій / назв), з якого було здійснено вибір. Будь-який стан, який потрібно передати разом із даними (наприклад, ім'я користувача, що використовується), слід перевірити на точці обробки, оскільки стан, можливо, сталий (якщо ви використовували якусь асинхронну схему, таку як черга повідомлень).
Шелакель

Хоча минуло два роки з моменту цієї публікації, я повинен зробити коментар на користь майбутніх глядачів: Дві речі заважали мені вашою відповіддю. Перегляд може відповідати одному ViewModel, але ViewModel може бути представлений кількома переглядами. По-друге, те, що ви описуєте, є антидіаграмою "Локатор послуг". IMHO, ви не повинні безпосередньо вирішувати модулі перегляду скрізь. Ось для чого і DI. Зробіть рішення в меншій мірі, наскільки це можливо. Дозвольте, наприклад, Caliburn зробити це за вас, наприклад.
Джоні Адаміт

1

Коротка відповідь на ваші запитання:

  1. Так, стан + поведінка призводить до цих проблем, але це справедливо для всіх ОО. Справжнім винуватцем є зв'язок ViewModels, що є своєрідним порушенням SRP.
  2. Статистично набрано, напевно. Але вам слід зменшити / усунути потребу в екземплярі ViewModels з інших ViewModels.
  3. Не те, що я обізнаний.
  4. Ні, але маю ViewModels з незв’язаним станом та поведінкою (як деякі посилання на моделі та деякі посилання ViewModel)

Довга версія:

Ми стикаємося з тією ж проблемою, і ми знайшли деякі речі, які можуть вам допомогти. Хоча я не знаю "магічного" рішення, ті речі трохи полегшують біль.

  1. Реалізуйте змінні моделі від DTO для відстеження та перевірки змін. Ці "Data" -ViewModels не повинні залежати від служб і не надходити з контейнера. Вони можуть бути просто "новими" редакторами, передані навколо і навіть можуть випливати з DTO. Підсумковою є реалізація моделі, характерної для вашої програми (як MVC).

  2. Розв’яжіть свої ViewModels. Каліфорн дозволяє легко з'єднати ViewModels разом. Він навіть пропонує це через свою модель екран / провідник. Але це з'єднання робить ViewModels важким для тестування, створює багато залежностей і найголовніше: Покладає тягар управління життєвим циклом ViewModel на ваші ViewModels. Один із способів їх роз'єднання - це використання чогось типу навігаційної служби або контролера ViewModel. Напр

    публічний інтерфейс IShowViewModels {void Show (об'єкт inlineArgumentsAsAnonymousType, string regionId); }

Ще краще це робити за допомогою якоїсь форми обміну повідомленнями. Але найважливіше - не обробляти життєвий цикл ViewModel від інших ViewModels. У MVC контролери не залежать один від одного, а в MVVM ViewModels не повинні залежати один від одного. Інтегруйте їх за допомогою інших способів.

  1. Використовуйте контейнери "строго" -типові / динамічні функції. Хоча можливо створити щось на кшталт INeedData<T1,T2,...>та застосувати безпечні для створення параметри створення, це не варто. Також створювати фабрики для кожного типу ViewModel не варто. Більшість контейнерів IoC пропонують рішення для цього. Ви отримаєте помилки під час виконання, але роз'єднання та перевірка одиниці того варті. Ви все ще робите тест на інтеграцію, і ці помилки помічаються легко.

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 # один.

Іншим часом слухайте свій інстинкт кишечника ...

Має багато ViewModels із станом та поведінкою дизайнерським запахом?

Чи є ваші моделі об'єктом на відображення таблиці? Можливо, ORM допоможе зіставити доменні об’єкти під час обробки бізнесу або оновлення декількох таблиць.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.