Как стать автором
Обновить

Комментарии 18

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

Вопрос: а почему нельзя было сделать IView наследником сразу QWidget? Реализация некоторых QMainWindow- и QDialog-специфичных вещей вручную — не проблема.
В этом случае сразу решается проблема с QtPlugin, например.

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

> передача представлений в Presenter посредством фабрики классов (таким образом можно легко менять стили)
Стили обычно меняются при помощи QApplication::setStyle(). Или это про другие стили?
>Вопрос: а почему нельзя было сделать IView наследником сразу QWidget?
Почему же нельзя, можно. Но таким образом вы «скажете», что все представления должны быть наследниками от QWidget. Если это подходит для конкретного проекта — то это нормально. Но если представление должно быть веб-страницей или консолью — то не покатит.

>Стили обычно меняются при помощи QApplication::setStyle(). Или это про другие стили?
Я имел ввиду общий вид приложения, т.е. фабрика может выдавать совершенно разные формы, главное чтобы они наследовались от интерфейсного класса
Я думал, вы говорите об различных itemview, а идея совсем в другом :)
Что ж, браво!
QML ещё слишком сырой, я пытался его использовать на новом проекте — зря потратил время. Там не реализован ни один стандартный контрол, писать же всё с нуля интересно с точки зрения изучения QML, но абсолютно непрактично. К тому же там отсутствуют некоторые важные моменты. В общем, не зря он всё ещё в лаборатории Qt значится. Пока что на нём хорошо получаются только интерфейсы а-ля «кавайный твиттер или фликер».
Так туда же можно встраивать обычные QWidget. Не в дизайнере, но ручками.
И из Labs его вытащили в QtDeclarative. А дизайнер нормальный уже на подходе, его обещали в qt-creator-2.1.
Судя по cahnge-log 4.7.1, работа над QML в разгаре (http://qt.nokia.com/developer/changes/changes-4.7.1/). Используем QML в проекте, полёт нормальный. Облом был один раз, когда они выпилили эффекты, но через qmlRegisterType() эффекты легко вернулись. Таким же образом можно и стандартные виджеты передавать из C++ в QML. Всё же для новых проектов действительно не рекомендовал бы его применять — думаю еще пол-годика надо подождать, пока допилят.
Я собираюсь дождаться QtCreator 2.1, а там ещё раз пощупать. Но да, вы правы, ещё минимум полгода до нормального состояния.
Вот здорово. Спасибо большое. На C# уже давненько применяем MVP а по QT пока ничего не встречал. Если есть возможность пожалуйста на примере каком-нибудь по-подробнее ещё несколько статеек.
Подскажите старику — а почему для связи view и presenter используются не сигналы и слоты, а передача указателя на интерфейс? Выбор чем-то обусловлен, или просто «так больше нравится»?
Пришлось бы создавать в Presenter-е дополнительные публичные слоты и сигналы, и коннектить View и Presenter в контексте вашего приложения. Также объект приложения должен будет ЗНАТЬ, как именно должны связываться View и Presenter. Мне кажется, это было бы не так удобно.
Пришлось бы создавать в Presenter-е дополнительные публичные слоты и сигналы, и коннектить View и Presenter в контексте вашего приложения.


Это да. Но ведь объем кода для описания интерфейса и для организации сигналов — одинаков? Зато сигналы по определению мультикаст — к одному презентеру из коробки можно подключить много view. А в вашем случае придется делать список указателей на view и вызывать их вручную.

Также объект приложения должен будет ЗНАТЬ, как именно должны связываться View и Presenter. Мне кажется, это было бы не так удобно.


А в текущем приложении разве не то же самое происходит когда указатель на view передается в презентер?
> Но ведь объем кода для описания интерфейса и для организации сигналов — одинаков?

Ну можно обойтись вообще без IView, да. Но тогда увеличится связанность, т.е. презентеру придется знать о конкретном View, а не об интерфейсе. IView для этого и применен, для уменьшения связанности. Подробней можно прочесть тут: www.rsdn.ru/article/patterns/ModelViewPresenter.xml#EGGAC, называется Inversion of Control.

> А в текущем приложении разве не то же самое происходит когда указатель на view передается в презентер?

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

Вообще, основная идея в том, чтобы однажды написав IView, можно было бы создавать конкретные View, при этом код основного приложения абсолютно не менялся. Например, есть фабрики классов, которые выдают конкретные View (окошки вашего приложения). Допустим фабрики ClassicViewFactory, GlamourViewFactory и WebViewFactory, которые наследуются от AbstractViewFactory. Далее, создавая на этапе запуска приложения конкретную фабрику (скажем прочитав соответствующий флаг из файла настроек приложения), имеем абсолютно разный внешний вид приложения. При этом код приложения вообще не меняется. При добавлении нового стиля приложения — добавляется новая фабрика, а в написанный код добавится лишь код:
if (settings.value(«style»).toString() == «web»)
m_viewFactory = new WebViewFactory(this); // где m_viewFactory объявлена как QAbstractViewFactory *m_viewFactory;
Ну можно обойтись вообще без IView, да. Но тогда увеличится связанность, т.е. презентеру придется знать о конкретном View, а не об интерфейсе. IView для этого и применен, для уменьшения связанности. Подробней можно прочесть тут: www.rsdn.ru/article/patterns/ModelViewPresenter.xml#EGGAC, называется Inversion of Control.


С inversion of control я немного знаком :). Что я бы хотел обсудить, если у вас есть немного времени: если не использовать интерфейс, то презентеру вообщем-то также не нужно знать о конкретном view. Давайте посмотрим на простой пример. Предположим, у нас есть view в лице небольшого окошка, которое может отображать текст и имеет кнопку «закрой меня». При использовании интерфейса это будет реализовано так (псевдокод, не компилируется):

class INotify
{
  public: virtual void setText( QString ) = 0;
  public signals: virtual void closeClicked() = 0;
};
 
class CNotify : public INotify
{
  public: void setText( QString )
  {
    //  Устанавливаем текст.
  }
 
  //  Вызывается Qt при клике на кнопку.
  private slots: void on_btn_close_clicked()
  {
    //  Рапортуем о клике на кнопку.
    emit closeClicked()
  }
}
 
class CPresenter
{
  public: CPresenter( INotify* view )
  {
    m_view = view;
    connect( view, SIGNAL(closeClicked()), this, SLOT(onClose()) );
  }
 
  private slots: void onClose()
  {
    //  Модель опустим, тут у нас как-бы логика.
    view->setText( tr( L"меня закрыли :(" ) );
  }
}
 
void main()
{
  CNotify notify;
  CPresenter presenter( notify );
}
 


Теперь то же самое при использовании сигналов и слотов:

class CNotify
{
  public: CNotify()
  {
    connect( ui->btn_close, SIGNAL(clicked), this, SIGNAL(closeClicked()) );
  }
 
  signals: void closeClicked();
 
  public slots: void setText( QString )
  {
    //  Устанавливаем текст.
  }
}
 
class CPresenter
{
  signals: void setText( QString );
 
  public slots: void onClicked()
  {
    emit setText( tr( L"меня закрыли :(" ) );
  }
}
 
void main()
{
  CNotify notify
  CPresenter presenter
  connect( notify, SIGNAL(closeClicked()), presenter, SLOT(onClicked()) );
  connect( presenter, SIGNAL(setText(QString)), notify, SLOT(setText(QString)) );
}
 


Как видно из исходника, в случае использования сигналов и слотов презентер также ничего не знает о view.

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


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

Вообще, основная идея в том, чтобы однажды написав IView, можно было бы создавать конкретные View, при этом код основного приложения абсолютно не менялся. Например, есть фабрики классов, которые выдают конкретные View (окошки вашего приложения). Допустим фабрики ClassicViewFactory, GlamourViewFactory и WebViewFactory, которые наследуются от AbstractViewFactory. Далее, создавая на этапе запуска приложения конкретную фабрику (скажем прочитав соответствующий флаг из файла настроек приложения), имеем абсолютно разный внешний вид приложения. При этом код приложения вообще не меняется. При добавлении нового стиля приложения — добавляется новая фабрика, а в написанный код добавится лишь код:


Паттерн «абстрактная фабрика», если не ошибаюсь? А оно в данном случае имеет какую-либо практическую ценность? Насколько я помню, внешний вид интерфейса в Qt меняется штатными средствами на уровне вызова одной функции «установить стиль для всего приложения». Что нам может дать абстрактная фабрика в реальных приложениях?
В приведенном вами примере, функция main() знает о том, как именно происходит связь между CNotify и CPresenter. Теперь при изменении интерфейсной части CNotify придется переписывать также функцию main(). Кроме того, метод QObject::connect() выполняется в динамике, в отличие передачи объекта по интерфейсу. А значит вы лишаетесь статической проверки того, насколько вы всё правильно сделали; например, если вы сигнал closeClicked() измените на closed(), программа скомпилируется, но не будет работать корректно (уже во время выполнения программы в консоль выведется информация о том, что не получилось соединиться с несуществующим сигналом closeClicked()). А теперь представьте, что View делает совершенно другой человек, более того — менее квалифицированный в программирование. Думаю статическая типизация сэкономит немало нервов.

> Как видно из исходника, в случае использования сигналов и слотов презентер также ничего не знает о view.
Знает, просто это не так заметно. На самом деле механизм сигналов и слотов — это реализация паттерна Observer на уровне MOC-компилятора, а значит объект подписки (Subject) знает о наблюдателях (Observer), а также знает указатели на функции уведомления у Observer.

> Ну почему же не знает — он ведь передает интерфейс
Я писал не о том, что объект не знает об объекте View, а о том, что он не знает, как именно происходит связь между View и Presenter

> А оно в данном случае имеет какую-либо практическую ценность? Насколько я помню, внешний вид интерфейса в Qt меняется штатными средствами на уровне вызова одной функции «установить стиль для всего приложения».

Насчет этого я уже писал в комментарии выше. Похоже, вы не до конца поняли, что дает MVP. setStyle() и setStyleSheet() позволяют с помощью QSS изменить внешний вид элементов. MVP позволяет вам изменить сам View, т.е. на окне могут быть совершенно другие элементы, расположенные совершенно по-другому. При этом setStyle() и setStyleSheet() никто не отменяет, MVP и страницы стилей это совершенно ортогональные вещи. Более того, с помощью MVP вы можете сделать представление не окошком, а консолью, или вообще веб-формой. Так что абстрактная фабрика действительно может пригодится в случае, если ваше приложение должно быть очень гибкое в плане UI, т.е. легко менять все View в приложении в зависимости от выбранного пользователем стиля. При чем останется возможность масштабирования системы, т.е. добавления новых видом View и фабрики для них. Наверное можно вообще сделать это дело плагинами через QtPlugin, предоставив таким образом разработчикам SDK для создания собственных View.
В приведенном вами примере, функция main() знает о том, как именно происходит связь между CNotify и CPresenter. Теперь при изменении интерфейсной части CNotify придется переписывать также функцию main().


В случае использования интерфейса, при изменении интерфейсной части CNotify придется переписывать также интерфейс. ИМХО, тут у подходов паритет?

роме того, метод QObject::connect() выполняется в динамике, в отличие передачи объекта по интерфейсу. А значит вы лишаетесь статической проверки того, насколько вы всё правильно сделали


В целом да. Хотя, по моему скромному мнению, для больших проектов это очень маленькая проблема :).

Знает, просто это не так заметно. На самом деле механизм сигналов и слотов — это реализация паттерна Observer на уровне MOC-компилятора, а значит объект подписки (Subject) знает о наблюдателях (Observer), а также знает указатели на функции уведомления у Observer.


Реализация, да. Но при этом процесс подписывания — внешний по отношению и к observer, и к observable. Тоесть с точки зрения observable у него есть только торчащий наружу делегат (signal), он не знает к кому этот делегат подключен.

Я писал не о том, что объект не знает об объекте View, а о том, что он не знает, как именно происходит связь между View и Presenter


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

Более того, с помощью MVP вы можете сделать представление не окошком, а консолью, или вообще веб-формой. Так что абстрактная фабрика действительно может пригодится в случае, если ваше приложение должно быть очень гибкое в плане UI, т.е. легко менять все View в приложении в зависимости от выбранного пользователем стиля.


Я таких «очень гибких» пока на практике не встречал. Зато встречал, например, необходмость из разных частей программы обращаться к фрагменту функциональности presenter. Например, для IM клиента нам в главном окне нужна полная функциональность работы со списком пользователей, для окна настроек — только получение списка, для модуля нотификаций — только получения имени по ID и так далее. Чем мне не очень нравятся интерфейсы — для каждого взаимодействия придется реализовывать ВЕСЬ интерфейс, так как в C++ ни informal protocol, ни optional interface не реализовано :(. Соответственно, опасаюсь что в проектах от миллиона строк кода это приведет к тому же, что в свое время было у COM: любое взаимодействие требует обязательной имплементации интерфейса о пятидесяти — ста методах О_О.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории