Синхронизация Model и ViewModel, когда приложение - Client

239
29 сентября 2017, 15:55

Добрый день.

Предыстория

Используя паттерн MVVM столкнулся с тем, что Model, который обычно считается DTO и в примерах то же самое, в моем случае больше подходит на Domain Model(или если быть точнее интерфейс, между Domain Model и Интерфейсом. Строиться архитектура На шаблоне Clean Architecture). С этим Domain Model работают и другие. По сути приложение на WPF является клиентом.

Вопрос

Проблема:

При таком подходе Model должна давать знать ViewModel об изменениях в себе. Исходя из MVVM напрямую она это делать не может, поэтому использовать можно способы:

  1. (Не рекомендуется)Реализация у Model интерфейса INotifyPropertyChanged и потом синхронизация из ViewModel(ViewModel этим занимается, Model просто предоставляет инструмент для этого)
  2. Использовать ObservableCollection в Model и в ViewModel и последующая синхронизация. Зачем связать 2 коллекции? Во View все равно ViewModel отдавать.

Сам вопрос:

Т.к я выбрал способ 2, то как мне синхронизировать 2 коллекции?

Первое, что пришло в голову - использовать интерфейс IModel(название сущности), вынести его отдельно. Создать коллекцию ObservableCollection в Model, при создании VM получать ссылку на эту коллекцию с тем же интерфейсом и View пусть View работает через интерфейс с этой коллекцией. Тем самым я не синхронизирую 2 коллекции, а оставляю одну. Для реализации подобного в Model есть DataModelController, который является синглотоном, который занимается хранением и выдачей данных. В итоге ViewModel через Di получает от этого DataModelController эту коллекцию.

Почему так? Логически все круто, View не знает о Model(знает об интерфейсе IModel), Model не зависит от ViewModel(классы Model реализуют просто интерфейс).

Но тут начинаются проблемы, если ViewModel несколько сложнее, чем просто переопределение Model. Допустим появляются команды, разная логика у самого ViewModel классов. Придется юзать (ViewModel)IModel, что ведет за собой приведение, что ведет за собой создание нового объекта при приведении(потому что так это работает), что ведет за собой разсинхронизацию(которую можно частично решить путем того, что при приведение сохраняется ссылка на Model(IModel точнее).

Думал еще создать абстрактный класс и чтобы Model и ViewModel наследовали его, тогда при приведении(вроде) это будет один и тот же объект, просто под разным соусом.

Также можно построить иерархию интерфейсов IViewModel:IModel. Тогда это будут тоже один и тот же объект под разным соусом. Но тогда, возможно, будет не очень хорошо с точки зрения поддержки. Да и глубокая иерархия наследования интерфейсов ничем не лучше иерархии наследования классов. Да и встает так же вопрос поддержки этого другими, ибо вроде это не популярно. Тобишь встает вопрос поддержки не мной.

Намного легче для понимания синхронизация двух коллекций.

Update 2

Уточню вопрос путем примеров и самих уточнений.

Как видно на картинке, Model может изменяться не только Client'ом, но и сервером. Т.е может Client2 тоже сидит и следит за сервером.

Один из сценариев: Client1 отправил изменения на Server, Server сохранил данные отправил всем подписчикам(Client2). Далее по схеме. Данные проходят через ServieCommunicationController и попадают в Model. Model должен неявно уведомить ViewModel. ViewModel обновляет View. И наоборот. Model на Client является синхронизирующийся с Server слоем.

Answer 1

Ваши вопросы слегка философские, разные люди вам ответят по-разному. Отвечу из личного опыта:

  1. Программы где Модель реализует INPC для нужд ViewModel в итоге становились сложно поддерживаемыми, сложно тестируемыми. Их приходилось переписывать.

  2. Пишу модели так, как будто других слоев не существует. Модель содержит как базовые объекты, так и бизнес-логику. Пишу тесты на модель.

  3. Сам факт того, ViewModel требует реализации INPC в модели уже говорит мне о том, что пора что-то менять. Изменение модели чаще всего является ответом на действие пользователя, то есть происходит из ViewModel. Может ли модель изменится другим путем, кроме как из ViewModel? Если да, то в модели должно быть соответствующее событие. Но чаще это не так. А раз ViewModel знает, что что-то произошло, зачем ждать реакции изменения с нижнего слоя, если можно просто запросить новое состояние измененной модели? Модель должна быть сама по себе.

  4. Как организовать коммуникацию между несколькими ViewModel, не затрагивая при этом модель? Я знаю несколько вариантов:

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

    • Сервис сообщает контроллеру, что надо обновить какие-то ViewModel.

    • Использовать паттерн EventAggregator

Ответ на конкретно ваш вопрос.

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

public class Model
{
    public List<string> Values { get; set; }
}
public class ViewModel : NotifyPropertyChangedBase
{
    private Model _model;
    public Model Model
    {
        get { return _model; }
        set
        {
            _model = value;
            UpdateValues();
        }
    }
    private void UpdateValues()
    {
        Values.Clear();
        Values.AddRange(_model.Values);
    }
    public ViewModel(Model model, IEventAggregator eventAggregator)
    {
        _model = model;
        eventAggregator.Subscribe<SomeProcessExecuted>(OnProcessExecuted);
    }
    private void OnProcessExecuted(object data)
    {
        UpdateValues();
    }
    public ObservableCollection<string> Values { get; } = new ObservableCollection<string>();
}
READ ALSO
Не читает из баз данных mvc

Не читает из баз данных mvc

Я создал базу и заполнил его с данными, но на экран не выводитсяКак правильно установить подключение:

164
DropDownList.Value

DropDownList.Value

Есть 2 системыТестовая и продуктивная

184
SolidColorBrush как Background Строки в DataGrid

SolidColorBrush как Background Строки в DataGrid

Есть WPF приложение в котором строки дата грида нужно подсветить в зависимости от содержимого одной ячейкиДля этих целей написал конвертер...

217
Как сделать общий метод двум классам C#

Как сделать общий метод двум классам C#

У нас есть общий метод

318