Какая архитектура программы лучше всего подходит для MDI-интерфейса?

Многодокументный интерфейс подразумевает, что в программе будет минимум две формы - Главная форма (контейнер) и форма, экземпляры которой будут создаваться внутри главной.

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

Но ведь программы с MDI-интерфейсом достаточно распространены, и наверняка для них есть уже выверенная и хорошо подходящая архитектура. Подскажите, пожалуйста, какая именно?

P.S. На данный момент мне видится наиболее удачной следующая конфигурация: введите сюда описание изображения


Ответы (2 шт):

Автор решения: Faraday

Для MDI проектов можно использовать один из следующих распространённых архитектурных паттернов:

  • MVC
  • MVP
  • MVVM
  • Document-View

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

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

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

введите сюда описание изображения

Взаимодействие с родительским контроллером можно реализовать через callback функцию, которую можно передать в конструкт при инициализации, либо в метод дочернего контроллера при его вызове. Логика выполнения примерно изображена на следующем рисунке (Простите за навыки рисования)

введите сюда описание изображения

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

Для сохранения зависимости и общения между контроллерами я бы всё же выбрал бы stateless подход. Суть в том, что бы всё взаимодействие проходило через базу данных либо сервисы с сохранением состояние. Но стоит отметить, такой подход вряд ли подойдёт, если вы хотите динамические изменения в программе без сохранения состояние. Структура такого подхода на следующем рисунке

введите сюда описание изображения

→ Ссылка
Автор решения: Pavel Mayorov

Есть много выверенных и подходящих архитектур (MVC, MVP, MVVM) у которых у всех общий недостаток - они слишком абстрактны и мало отвечают на вопрос о том, как же на самом деле следует писать код.

На самом деле, MVC настолько абстрактна, что на Хабре до сих пор периодически спорят чем именно является форма - моделью, контроллером или видом

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

Сделать это можно тремя способами.

Способ 1. Через общую модель.

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

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

Способ 2. Через события

Дочерняя форма может объявить событие, а главная форма - на него подписаться.

Способ 3. Через интерфейс

На случай, когда событий недостаточно или просто слишком много.

Дочерняя форма может запросить в конструкторе некоторый интерфейс (скажем, IChildFormOwner или IChildFormHost), а главная форма - его реализовать (напрямую или через внутренний класс).

Способ 4. Через задачи (Task)

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

    private readonly TaskCompletionSource tcs = new();

    private void bthOk_Click(object sender, EventArgs e)
    {
        tcs.TrySetResult();
        Close();
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        tcs.TrySetCanceled();
        Close();
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        tcs.TrySetCanceled();
        base.OnFormClosed(e);
    }

    public Task ShowAsync()
    {
        Show();
        Activate();
        return tcs.Task;
    }
→ Ссылка