Проектируя архитектуру одного проекта, остановился на паттерне MVP — подкупила возможность легко менять ui, а также простота покрытия тестами. Все примеры реализации MVP, что я нашёл в сети, были на C#. При реализации на Qt возникла пара неочевидных моментов, решение которых было успешно найдено. Собранная информация ниже.
Как следует из [1] и [2], шаблон проектирования MVP является модификацией MVC, примененной впервые в компании IBM в 90-х годах прошлого века при работе над объектно-ориентированной операционной системой Taligent. Позднее MVP подробно описал Майк Потел.
Чтобы увидеть, в чём же отличие MVP от MVC, можно посмотреть соответствующие параграфы [3]: MVC, MVP
В общем и целом, MVP уже и так применяется в Qt в неявном виде, как это показано в [4]:

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

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

Рассмотрим каждый класс:
На этапе «рисования квадратиков» всё понятно. Проблемы у меня возникли при попытке реализации.
Также стоит заметить, что при реализации IView нужен только заголовочный (.h) файл. Если для IView создан .cpp файл — его необходимо удалить, иначе могут возникнуть проблемы при компиляции.
В принципе, этой информации достаточно, чтобы самостоятельно реализовать MVP в Qt, но я приведу простой пример (только реализация MVP, без рассмотрения взаимодействия в контексте реального приложения).
В архиве 3 примера, это одно и то же приложение, но с дополнениями в каждом примере.
Весь код комментирован в Doxygen-стиле, т.е. вы легко можете сгенерировать документацию с помощью Doxywizard.
Скачать примеры можно тут
Вот список интересных вопросов по теме, которые не вошли в статью:
В комментариях приветствуется любая информация по этим вопросам.
История MVP
Как следует из [1] и [2], шаблон проектирования MVP является модификацией MVC, примененной впервые в компании IBM в 90-х годах прошлого века при работе над объектно-ориентированной операционной системой Taligent. Позднее MVP подробно описал Майк Потел.
Чтобы увидеть, в чём же отличие MVP от MVC, можно посмотреть соответствующие параграфы [3]: MVC, MVP
Реализация MVP в Qt
В общем и целом, MVP уже и так применяется в Qt в неявном виде, как это показано в [4]:

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

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

Рассмотрим каждый класс:
- IView — интерфейсный класс, определяющий методы и сигналы, которые должен реализовывать конкретный View (см. Inversion of Control в [3])
- Ui::View — класс, сгенерированный по .ui файлу Qt-дизайнера
- View — конкретное представление, наследуемое от QWidget (или его потомка) и от IView. Содержит логику GUI, т.е. поведение объектов (например, их анимацию)
- Model — модель данных предметной области (Domain Model); содержит переменные, флаги, модели таблиц и т.д.
- Presenter — представитель; реализует взаимодействие между моделью и представлением. Также через него происходит взаимодействие с внешним миром, т.е. с основным приложением, с сервером (например, через слой служб приложения) и т.д.
На этапе «рисования квадратиков» всё понятно. Проблемы у меня возникли при попытке реализации.
- При написании IView необходимо объявлять сигналы. Но для этого IView должен быть унаследован от QObject. Далее при попытке наследовать View одновременно от QWidget и IView возникала ошибка. Оказалось, что класс не может быть унаследован одновременно от двух QObject-объектов (не помогло даже виртуальное наследование). Как обойти эту проблему показано в [5]: ссылка. Таким образом, IView не наследуется от QObject и просто объявляет сигналы как полностью виртуальные (абстрактные) методы в секции public. Это вполне логично, т.к. сигнал — это тоже функция, по вызову которой происходит оповещение наблюдателей через их слоты (см. паттерн Observer).
- Еще одна проблема возникла при привязке сигналов 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 примера, это одно и то же приложение, но с дополнениями в каждом примере.
- Реализация MVP
- Добавлена возможность создавать несколько представлений
- Полная синхронизация между представлениями при изменении данных
Весь код комментирован в Doxygen-стиле, т.е. вы легко можете сгенерировать документацию с помощью Doxywizard.
Скачать примеры можно тут
Out of scope
Вот список интересных вопросов по теме, которые не вошли в статью:
- тестирование модулей полученной системы
- реализация View в виде библиотек (с помощью QtPlugin) в контексте MVP
- реализация View с помощью QtDeclarative (QML) в контексте MVP
- передача представлений в Presenter посредством фабрики классов (таким образом можно легко менять стили)
В комментариях приветствуется любая информация по этим вопросам.
Источники
- http://www.rsdn.ru/article/patterns/generic-mvc2.xml
- http://en.wikipedia.org/wiki/Model-view-presenter
- http://www.rsdn.ru/article/patterns/ModelViewPresenter.xml
- http://thesmithfam.org/blog/2009/09/27/model-view-presenter-and-qt/
- http://doc.trolltech.com/qq/qq15-academic.html
- http://developer.qt.nokia.com/forums/viewthread/284