MVVM и схема обслуживания

14

Я строю приложение WPF, используя шаблон MVVM. Прямо сейчас мои viewmodels вызывают сервисный уровень для извлечения моделей (как это не относится к viewmodel) и преобразования их в viewmodels. Я использую инъекцию конструктора, чтобы передать сервис, необходимый для модели представления.

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

Я думаю о нескольких решениях:

  1. Создание синглтона сервисов (IServices), содержащего все доступные сервисы в качестве интерфейсов. Пример: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). Таким образом, у меня нет огромного конструктора с кучей параметров сервисов.

  2. Создание фасада для сервисов, используемых viewModel, и передача этого объекта в ctor моей viewmodel. Но тогда мне придется создать фасад для каждой из моих сложных моделей, и это может быть немного ...

Как вы думаете, что является «правильным» способом реализации такой архитектуры?

альфа-альфа
источник
Я думаю, что «правильный» способ сделать это состоит в том, чтобы создать отдельный слой, который вызывает сервисы и делает все необходимое для создания ViewModel. Ваши ViewModels не должны нести ответственность за создание самих себя.
Эми Бланкеншип
@AmyBlankenship: Модели представлений не должны (или даже обязательно иметь возможность) создавать себя, но неизбежно иногда будут нести ответственность за создание других моделей представлений . Контейнер IoC с автоматической заводской поддержкой - огромная помощь.
Aaronaught
«Будет иногда» и «должен» два разных животных;)
Эми Бланкеншип
@AmyBlankenship: Вы предлагаете, чтобы модели представлений не создавали другие модели представлений? Это жесткая таблетка, которую нужно проглотить. Я могу понять, говоря, что модели представлений не следует использовать newдля создания других моделей представлений, но представьте себе что-то столь же простое, как приложение MDI, где нажатие на кнопку или меню «новый документ» добавит новую вкладку или откроет новое окно. Оболочка / проводник должны иметь возможность создавать новые экземпляры чего-либо , даже если это скрыто за одним или несколькими слоями косвенного обращения.
Aaronaught
Ну, конечно, он должен иметь возможность запрашивать, чтобы где-то был сделан View. Но сделать это самому? Не в моем мире :). Но опять же, в мире, в котором я живу, мы называем ВМ «Модель представления».
Эми Бланкеншип

Ответы:

22

На самом деле оба эти решения плохие.

Создание синглтона сервисов (IServices), содержащего все доступные сервисы в качестве интерфейсов. Пример: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). Таким образом, у меня нет огромного конструктора с кучей параметров сервисов.

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

Создание фасада для сервисов, используемых viewModel, и передача этого объекта в ctor моей viewmodel. Но тогда мне придется создать фасад для каждой из моих сложных моделей, и это может быть немного ...

Это не столько анти-паттерн, сколько запах кода. По сути, вы создаете объект параметра , но смысл паттерна рефакторинга PO состоит в том, чтобы иметь дело с наборами параметров, которые используются часто и во многих разных местах , тогда как этот параметр будет использоваться только один раз. Как вы упомянули, это привело бы к большому раздуванию кода без какой-либо реальной выгоды, и было бы плохо с большим количеством контейнеров IoC.

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

Подумайте, как бы вы протестировали одну из этих моделей представлений. Насколько большим будет ваш установочный код? Сколько вещей нужно инициализировать, чтобы это работало?

Многие люди, начинающие с MVVM, пытаются создать модели представления для всего экрана , что в корне неверно. MVVM - это все о композиции , и экран с множеством функций должен состоять из нескольких разных моделей представлений, каждая из которых зависит только от одной или нескольких внутренних моделей / сервисов. Если им нужно общаться друг с другом, вы делаете это через pub / sub (брокер сообщений, шина событий и т. Д.)

Что вам действительно нужно сделать, так это провести рефакторинг моделей представлений, чтобы они имели меньше зависимостей . Затем, если вам нужен агрегатный «экран», вы создаете другую модель представления для агрегирования моделей меньшего размера. Эта модель агрегированного представления сама по себе не должна делать ничего особенного, поэтому она, в свою очередь, также довольно проста для понимания и тестирования.

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

Aaronaught
источник
Да, это то, что я, вероятно, в конечном итоге сделаю! Большое спасибо, сэр.
Альфа-Альфа
Ну, я уже предполагал, что он уже попробовал это, но не удалось. @ alfa-alfa
Euphoric
@Euphoric: Как вам "не удается" в этом? Как сказал бы Йода: «Делай или не делай», нет никакой попытки.
Aaronaught
@Aaronaught Например, ему действительно нужны все данные в единой модели представления. Может быть, у него есть сетка и разные столбцы приходят из разных сервисов. Вы не можете сделать это с композицией.
Euphoric
@Euphoric: На самом деле, вы можете решить это с помощью композиции, но это можно сделать ниже уровня модели представления. Это просто вопрос создания правильных абстракций. В этом случае вам нужен только один сервис для обработки начального запроса, чтобы получить список идентификаторов, и последовательность / список / массив «обогатителей», которые аннотируют своей собственной информацией. Сделайте сетку самой по себе своей моделью представления, и вы эффективно решили проблему с двумя зависимостями, и ее чрезвычайно легко протестировать.
Aaronaught
1

Я мог бы написать книгу об этом ... на самом деле я;)

Во-первых, не существует универсально «правильного» способа делать вещи. Вы должны принять во внимание другие факторы.

Возможно, ваши услуги слишком мелкозернистые. Обертывание Сервисов с Фасадами, которые предоставляют интерфейс, который будет использовать конкретная Viewmodel или даже кластер связанных ViewModels, может быть лучшим решением.

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

Фактически, то, что я видел во многих архитектурах, в конечном итоге превратилось в шину сообщений, построенную вокруг чего-то вроде шаблона Event Aggregator. Тестирование там легко, потому что ваш тестовый класс просто регистрирует слушателя в советнике и запускает соответствующее событие в ответ. Но это прогрессивный сценарий, который требует времени, чтобы расти. Я говорю начать с объединяющего фасада и идти оттуда.

Майкл Браун
источник
Массивный сервисный фасад сильно отличается от брокера сообщений. Это почти на противоположном конце спектра зависимости. Отличительной чертой этой архитектуры является то, что отправитель ничего не знает о получателе, и может быть много получателей (pub / sub или multicast). Возможно, вы путаете это с «удаленным взаимодействием» в стиле RPC, которое просто предоставляет традиционную услугу по удаленному протоколу, и отправитель все еще связан с приемом, как физически (адрес конечной точки), так и логически (возвращаемое значение).
Aaronaught
Сходство состоит в том, что фасад действует как маршрутизатор, вызывающая сторона не знает, какая служба / службы обрабатывает вызов, точно так же, как клиент, отправляющий сообщение, не знает, кто обрабатывает сообщение.
Майкл Браун
Да, но тогда фасад - это объект Бога . У него есть все зависимости, которые есть у моделей представления, вероятно, больше, потому что он будет использоваться несколькими. По сути, вы устранили преимущества слабой связи, над которой вы так усердно работали, потому что теперь, когда что-то касается мегафасада, вы не понимаете, от какой функциональности это зависит. Изображение написания модульного теста для класса, который использует фасад. Вы создаете макет для фасада. Теперь, какие методы вы издеваетесь? Как выглядит ваш установочный код?
Aaronaught
Это очень отличается от брокера сообщений, потому что брокер также ничего не знает о реализации обработчиков сообщений . Он использует IoC под капотом. Фасад знает все о получателях, потому что он должен переадресовывать звонки им. Шина имеет нулевую связь; фасад имеет неприлично высокое эфферентное сцепление. Почти все, что вы меняете, где угодно, повлияет и на фасад.
Aaronaught
Я думаю, что некоторая путаница здесь - и я вижу это довольно часто - это то, что означает зависимость . Если у вас есть класс, который зависит от одного другого класса, но вызывает 4 метода этого класса, у него есть 4 зависимости, а не 1. Помещение всего этого за фасад не меняет количество зависимостей, а только усложняет их понимание. ,
Aaronaught
0

Почему бы не объединить оба?

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

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

Euphoric
источник
Это было бы лучшим ответом, если бы вы предоставили пример в псевдо-C #.
Роберт Харви