Pull to refresh

Реализация Model-View-Presenter в Qt

Reading time 3 min
Views 27K
Проектируя архитектуру одного проекта, остановился на паттерне MVP — подкупила возможность легко менять ui, а также простота покрытия тестами. Все примеры реализации MVP, что я нашёл в сети, были на C#. При реализации на Qt возникла пара неочевидных моментов, решение которых было успешно найдено. Собранная информация ниже.

История MVP


Как следует из [1] и [2], шаблон проектирования MVP является модификацией MVC, примененной впервые в компании IBM в 90-х годах прошлого века при работе над объектно-ориентированной операционной системой Taligent. Позднее MVP подробно описал Майк Потел.

Чтобы увидеть, в чём же отличие MVP от MVC, можно посмотреть соответствующие параграфы [3]: MVC, MVP

Реализация MVP в Qt


В общем и целом, MVP уже и так применяется в Qt в неявном виде, как это показано в [4]:

image

Но такая реализация MVP имеет ряд недостатков:
  • невозможно создать несколько представлений для одного представителя (Presenter)
  • невозможно использовать паттерн Inversion of Control, описанный в [3] (тут), т.к. в данной схеме View генерируется автоматически и не может быть унаследовано от интерфейса

Полная реализация MVP будет выглядеть так:

image

Отсюда диаграмма классов:

image

Рассмотрим каждый класс:
  • IView — интерфейсный класс, определяющий методы и сигналы, которые должен реализовывать конкретный View (см. Inversion of Control в [3])
  • Ui::View — класс, сгенерированный по .ui файлу Qt-дизайнера
  • View — конкретное представление, наследуемое от QWidget (или его потомка) и от IView. Содержит логику GUI, т.е. поведение объектов (например, их анимацию)
  • Model — модель данных предметной области (Domain Model); содержит переменные, флаги, модели таблиц и т.д.
  • Presenter — представитель; реализует взаимодействие между моделью и представлением. Также через него происходит взаимодействие с внешним миром, т.е. с основным приложением, с сервером (например, через слой служб приложения) и т.д.

На этапе «рисования квадратиков» всё понятно. Проблемы у меня возникли при попытке реализации.
  1. При написании IView необходимо объявлять сигналы. Но для этого IView должен быть унаследован от QObject. Далее при попытке наследовать View одновременно от QWidget и IView возникала ошибка. Оказалось, что класс не может быть унаследован одновременно от двух QObject-объектов (не помогло даже виртуальное наследование). Как обойти эту проблему показано в [5]: ссылка. Таким образом, IView не наследуется от QObject и просто объявляет сигналы как полностью виртуальные (абстрактные) методы в секции public. Это вполне логично, т.к. сигнал — это тоже функция, по вызову которой происходит оповещение наблюдателей через их слоты (см. паттерн Observer).
  2. Еще одна проблема возникла при привязке сигналов IView в классе Presenter. Дело в том, что Presenter содержит ссылку на IView (конкретный View добавляется в Presenter на этапе выполнения, но хранится как IView — используется полиморфизм). Но для соединения сигналов и слотов используется статический метод QObject::connect(), принимающий в качестве объектов только наследников от QObject, а IView не является таковым. Но мы то знаем, что любой наш View будет им являться. Так что проблема решается динамическим приведением типов, как это показано в [6]:

    QObject *view_obj = dynamic_cast<QObject*>(m_view); // где m_view - IView
    QObject::connect(view_obj, SIGNAL(okActionTriggered()),
                     this, SLOT(processOkAction()));


Также стоит заметить, что при реализации IView нужен только заголовочный (.h) файл. Если для IView создан .cpp файл — его необходимо удалить, иначе могут возникнуть проблемы при компиляции.

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

Простой пример использования MVP в Qt


В архиве 3 примера, это одно и то же приложение, но с дополнениями в каждом примере.
  1. Реализация MVP
  2. Добавлена возможность создавать несколько представлений
  3. Полная синхронизация между представлениями при изменении данных

Весь код комментирован в Doxygen-стиле, т.е. вы легко можете сгенерировать документацию с помощью Doxywizard.

Скачать примеры можно тут

Out of scope


Вот список интересных вопросов по теме, которые не вошли в статью:
  • тестирование модулей полученной системы
  • реализация View в виде библиотек (с помощью QtPlugin) в контексте MVP
  • реализация View с помощью QtDeclarative (QML) в контексте MVP
  • передача представлений в Presenter посредством фабрики классов (таким образом можно легко менять стили)

В комментариях приветствуется любая информация по этим вопросам.

Источники


  1. http://www.rsdn.ru/article/patterns/generic-mvc2.xml
  2. http://en.wikipedia.org/wiki/Model-view-presenter
  3. http://www.rsdn.ru/article/patterns/ModelViewPresenter.xml
  4. http://thesmithfam.org/blog/2009/09/27/model-view-presenter-and-qt/
  5. http://doc.trolltech.com/qq/qq15-academic.html
  6. http://developer.qt.nokia.com/forums/viewthread/284
Tags:
Hubs:
+37
Comments 18
Comments Comments 18

Articles