Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: Lagovas от Август 03, 2011, 19:25



Название: Разделение логики и GUI
Отправлено: Lagovas от Август 03, 2011, 19:25
Только начал осваивать Qt и тому подобное. Сразу хочется учится программировать правильно, ведь учиться легче чем переучиваться. Сейчас не работаю и не пишу продакшн проекты, а для себя. И мне кажется что в реальных приложениях намного лучше изначально отделять логику и отображение. Так вот, подскажите что почитать, что попробовать, в каком направлении двигаться. И возможно ли с Qt так делать. Насколько я пока понимаю как это делается, пишется в куте форма, с сигналами и методами которыми можно с ней взаимодействовать. А "логика" должна взаимодействовать с формой. Так вот, если так делать, то все классы логики должны наследоваться от QObject? Ведь вроде без него нельзя работать с сигналами, а если так, то вся фишка гуи в куте теряется. Скорее всего я чего то недопонимаю, и прошу раздуплить. Заранее благодарен.


Название: Re: Разделение логики и GUI
Отправлено: Авварон от Август 04, 2011, 02:24
Так вот, если так делать, то все классы логики должны наследоваться от QObject? Ведь вроде без него нельзя работать с сигналами, а если так, то вся фишка гуи в куте теряется.
Не понял. Какая фишка?


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 04, 2011, 06:02
Классическая проверка на вшивость: есть некоторое приложение с GUI, теперь требуется консольная версия (без GUI). И вот тут выясняется что "ой" :) По существу Вы спрашиваете: а как сделать чтобы мои приложения имели хорошую, гибкую архитектуру? Ответ на этот вопрос мне неизвестен. Я стараюсь решать это на уровне файлов и классов. Т.е. есть cpp файлы "с UI и без". Это примитивное разделение но работает неплохо.  Расчетные" классы не знают о существовании UI и обычно подаются в UI как указатели/ссылки. Часто приходится делать небольшие адаптеры для развязки. Слот/сигнал цветет пышным цветом именно в UI, в расчетной части его применение гораздо скромнее, а часто можно спокойно обойтись и без него. 

Тему Вы затронули интересную, но Ваш вопрос слишком общий. Возможно с конкретным примером обсуждение было бы более интересным/продуктивным.     


Название: Re: Разделение логики и GUI
Отправлено: SeverusSnape от Август 04, 2011, 09:51
В качестве конкретного примера можно посмотреть на великолепную читалку FBReader - которая реализована по такому принципу


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 01:22
Просто есть ведь разные паттерны проектирования и тому подобное, много книг по архитектуре, думал уже есть какие то отлаженные механизмы разделения. Плюс хотел узнать возможно ли так и как вы это сами делаете.


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 09:59
Хм, web же. Там гуи на языке html + интерактивность с помощью javascript, для логики используется серверный язык (php, python, java ....). Вот вам шаблон проектирования: сервер логики и гуи клиент.
Распространенный шаблон номер два: фронт-енд к базе данных. ГУИ представляет из себя формочки для ввода данных, с некоторой интерактивностью, а тажке отчетики длы вывода данных. Сами же данные храняться в СУБД и агрегируются с помощью процедур в триггерах.


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 11:43
Ну когда оно сделано и работает - то конечно все ясно :). А вот простейший пример: при выполнении какой-то ф-ии или метода произошла ошибка. Нужно показать  пользователю модальный диалог и вернуть false

Код
C++ (Qt)
if (!... ) {           // нашли ошибку
sprintf(buf, "Error reading file %s, line %d, unexpected token (%c) ", fName, lineNo, token);  // сообщение
QMessageBox(QMessageBox::Warning, "Error", buf).exec();  // показали модальный диалог
return false;  //  вышли
}
 
Как мы видим UI проникло/просочилось в cpp файл который занят разбором текста (т.е. никакого отношения к UI иметь не должен). Как этого избежать?


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 11:47
Я хоть с потоками мало работал, но мне кажется можно ошибки писать в std::err или чет такое, а там где гуи, читать этот поток и выводить. На крайняк в файл, и с файла читать.


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 11:59
Lagovas прав.
QMessageBox в алгоритмах зло, ибо потенциально может являться спамом.
Любой алгоритм должен журналироваться, а журнал перенаправлятся в виджет. Вот и вся логика.
Если есть такое требование в ТЗ, то ТЗ нужно редактировать.


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 12:06
А какой метод лучше, сделать отдельный метод в гуи классе, который принимает параметром ошибки для вывода и использовать его в логике, все же писать в поток и емитить сигнал, о том что надо показать еррор либо же что бы метод гуя был в отдельном потоке и по таймеру проверял еррор лог?


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 12:14
QMessageBox в алгоритмах зло, ибо потенциально может являться спамом.
Все согласны что зло  :)
Любой алгоритм должен журналироваться, а журнал перенаправлятся в виджет. Вот и вся логика.
Прошу показать "UI - независимую" версию фрагмента приведенного в посте #6


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 12:14
Я реализовывал через log4qt с помощью сигнал-слотового соединения. Соединение потокобезопасное, поэтому как алгоритм будет реализован не важно.


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 12:22
Код:
StaticLogAppender appender
{
   slot:
       addMessage(string) {emit message(string);}
   signals:
       message(string);
};

handler (string){
     appender.addMessage(string)
}

main
{
   installQtDebugHandlers(handler);

   QWidget logwindow;
   connect(appender, SIGNAL(message), logwindow, SLOT(addMessage), Qt::QueuedConnection);
}

algorythm()
{
   qDebug() << error;
}


Название: Re: Разделение логики и GUI
Отправлено: kambala от Август 07, 2011, 12:31
Прошу показать "UI - независимую" версию фрагмента приведенного в посте #6
например в Objective-C часто используется передача в функцию последним параметром указатель на NSError *, который в случае неудачи выполнения функции становится != nil и содержит информацию об ошибке, а в случае успеха - == nil.

другой вариант - возвращать из функции QString, а не bool (в случае успеха - QString(), неудачи - сообщение с ошибкой), но тогда не получится красивого
Код
C++ (Qt)
if (myfunction())
{
   ...
}


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 12:36
разве не получится? Разве пустая строка не будет считаться false?


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 12:45
Код:
   connect(appender, SIGNAL(message), logwindow, SLOT(addMessage), Qt::QueuedConnection);
}

algorythm()
{
   qDebug() << error;
}
Если это ответ на мой вопрос, то как быть с "чисто UI" параметрами (иконка QMessageBox::Warning, и заголовок окна "Error")? Далеко не всегда UI есть "просто лог вставленный в окошко". Как поможет лихое перенаправление qDebug() если потребуется спросить Yes/No/Cancel?


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 13:06
Для интерактивности необходим дополнительный код. Могу дополнить вышеприведенный листинг. Только моя реализация приблизит архитектуру к разделению на консольную версию программы и гуи фронт-енда.


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 13:47
другой вариант - возвращать из функции QString, а не bool (в случае успеха - QString(), неудачи - сообщение с ошибкой), но тогда не получится красивого
По-моему более естественно так
Код
C++ (Qt)
if (value > limit)    // обнаружили
// сохраняем текст ошибки (который потом можно получить GetLastError и возвращаем false)
return theErrorMgr::SetLastError("overflow");  
 
А theErrorMgr находится в др файле и умеет работать с UI и без. В принципе если просто "текст ошибки" то проблем нет, и, на мой взгляд, задействовать qDebug() ни к чему. Но в том-то и дело что UI всегда имеет "мелкие подробности" которые специфичны и в общую схему упорно не укладываются  :) (напр титул окна, иконка(и), какой-то (дополнительный) хелп текст, что-то надо выравнять направо и.т.п)

Могу дополнить вышеприведенный листинг. Только моя реализация приблизит архитектуру к разделению на консольную версию программы и гуи фронт-енда.
Ну если нетрудно, покажите (или расскажите в чем там смысл)


Название: Re: Разделение логики и GUI
Отправлено: lit-uriy от Август 07, 2011, 13:48
>>Как мы видим UI проникло/просочилось в cpp файл который занят разбором текста
>>(т.е. никакого отношения к UI иметь не должен). Как этого избежать?
Я уже давно не использую "ничейных" функций, только классы.
И использую, при необходимости, строковые переменные типа "последняя ошибка", метод класса возвращает ЛОЖЬ, при этом в вызывающем коде зовётся метод типа "Дай последнюю ошибку". Ну а дальше, зависит от того, является ли вызывающий код человеко-машинным интерфейсом (консольным или графическим, не важно) или нет.


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 14:21
Я уже давно не использую "ничейных" функций, только классы.
И использую, при необходимости, строковые переменные типа "последняя ошибка", метод класса возвращает ЛОЖЬ, при этом в вызывающем коде зовётся метод типа "Дай последнюю ошибку".
То ясно (см. мой предыдущий пост), толкуем о том что "текст ошибки" хорош для консоли, но (как правило) это еще не все что нужно UI


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 14:23
Цитировать
или расскажите в чем там смысл
Смысл в том чтобы код ui и код алгоритма обменивались сообщениями в блокирующих/неблокирующих режимах в зависимости от потребностей. Код алгоритма вызывает write("Continue?"); if read() == n {stop}. Код ui показывает сообщение ну и, если оно требует ответа, ждет ответ. Ответ отправляется коду алгоритма.


Название: Re: Разделение логики и GUI
Отправлено: Авварон от Август 07, 2011, 15:03
делать классы реентрантными, возвращать ф-ией бул и иметь ф-ию lastError()
для асинхронных классов - сигнал эррор()


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 15:21
Цитировать
или расскажите в чем там смысл
Смысл в том чтобы код ui и код алгоритма обменивались сообщениями в блокирующих/неблокирующих режимах в зависимости от потребностей. Код алгоритма вызывает write("Continue?"); if read() == n {stop}. Код ui показывает сообщение ну и, если оно требует ответа, ждет ответ. Ответ отправляется коду алгоритма.
Слот/сигнал никакой не magic и "зависимостей" не развязывает. Да, с его помощью легко избавиться от UI "в явном виде", т.е. файл не требует <QtGui>, но от этого не станет легче. Как Вы сообщите вызывающему оте "мелкие подробности" (которые Вы стараетесь не замечать - поправьте если не так). Ведь даже в простейшем случае нужен заголовок окна и тип иконки. Начнете плодить больше слотов/сигналов? Так Вы скоро устанете и, когда сроки прижмут, начнете лепить UI напрямую

делать классы реентрантными, возвращать ф-ией бул и иметь ф-ию lastError()
для асинхронных классов - сигнал эррор()
Цитировать
- почему ты ищешь только под фонарем?
- да потому что только там светло!
:)


Название: Re: Разделение логики и GUI
Отправлено: lit-uriy от Август 07, 2011, 15:22
Можно завести вспомогательный класс описывающий ошибку, удобный и для графического интерфейса и для текстового.


Название: Re: Разделение логики и GUI
Отправлено: lit-uriy от Август 07, 2011, 15:25
>>нужен заголовок окна и тип иконки
заголовок, если это не имя приложения, то тип сообщения (ошибка, предупреждение, ...). А тип сообщения однозначно связан с картинкой.
Из логики программы за ранее известно, что если сейчас функция вернёт ЛОЖЬ, то - ошибка (предупреждение,...).


Название: Re: Разделение логики и GUI
Отправлено: asvil от Август 07, 2011, 15:40
Цитировать
Как Вы сообщите вызывающему оте "мелкие подробности"
Енто называется протокол общения, в моем примере текстовый. Употреблять бинарный по желанию.

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


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 16:10
>>нужен заголовок окна и тип иконки
заголовок, если это не имя приложения, то тип сообщения (ошибка, предупреждение, ...). А тип сообщения однозначно связан с картинкой.
Из логики программы за ранее известно, что если сейчас функция вернёт ЛОЖЬ, то - ошибка (предупреждение,...).
Ну допустим, чуть сэкономили - вместо 2 параметров 1, но это так, "на спичках" - проблема-то остается. UI будет порождать десятки подобных параметров,  отделаться просто "текстом ошибки" (a la Linux) никак не удастся

Можно завести вспомогательный класс описывающий ошибку, удобный и для графического интерфейса и для текстового.
Этот класс быстро превращается в "помойную яму" (типа Вындоуз реестра) в которую каждый конкретный интерфейс льет свое. В одном UI потребуется одно, в другом UI - другое.  Получается что вызывающий должен знать все подробности всех интерфейсов - расписываемся в беспомощности.

Цитировать
Как Вы сообщите вызывающему оте "мелкие подробности"
Енто называется протокол общения, в моем примере текстовый. Употреблять бинарный по желанию.

lit-uriy класс то можно, вопрос в том как оптимальнее наследить в коде, что бы реализовать разделение мух и котлет, и чтобы и те и другие друг про друга знали.
Рад что Михаил на Линуксе меня понимает (а не просто изрекает прописные истины :))  Давайте исполним протокол, пусть он будет не perfect, это нормально. А то отделаться "текстом ошибки" всем ясно. Ваши предложения?


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 16:28
Много ли типов ошибок? Сделать какой нить доступный enum, и передавать как параметр. А вообще, разве при написании гуя, не ясно какие там могут быть ошибки? Заглавие делать обобщенным, типа Ошибка в работе с файлом, а в тексте уже подробности, которые предоставляет логика.


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 07, 2011, 16:44
Много ли типов ошибок? Сделать какой нить доступный enum, и передавать как параметр. А вообще, разве при написании гуя, не ясно какие там могут быть ошибки? Заглавие делать обобщенным, типа Ошибка в работе с файлом, а в тексте уже подробности, которые предоставляет логика.
Конечно "типов ошибок" совсем немного - но передавать этот параметр надо. Но наивно думать что отделаемся "текстом ошибки" + "типом ошибки" - это мы просто "воспроизвели ситуацию на следующем шаге". Очень быстро появляются "исключения" которые требуют 3-й параметр (следующий шаг). Когда число таких "шагов" переваливает за 8-10, думается типа "ну надо же что-то делать, как-то обобщать"


Название: Re: Разделение логики и GUI
Отправлено: kambala от Август 07, 2011, 16:52
можно попробовать паковать все в словарь, список, или строку с особыми разделителями


Название: Re: Разделение логики и GUI
Отправлено: Lagovas от Август 07, 2011, 17:02
Лучше взять какой то лог проги, и посмотреть как это логируется. У сисадминов посмотреть и проанализировать)
А вообще логично сделать какой то базовый класс ерора, в котором описаны самые основные поля, которые могут потребоваться. И логика должна их использовать, заполнять по максимуму, а гуи использовать. Плюс здесь же будет возможность пронаследовать и добавить конкретные возможности которые нужны в частном случае. А вообще имхо преувеличиваете. Можно выделить группы ошибок, и от них отталкиваться. Ерроры которые не входят в группу, должны оформляться в ручную. Заглавие, текст и т.п. А самые частые сделать с значениями по умолчанию, имхо.


Название: Re: Разделение логики и GUI
Отправлено: Igors от Август 08, 2011, 07:19
можно попробовать паковать все в словарь, список, или строку с особыми разделителями
Паковать тоже пробовал (и наблюдал как это делают другие :))

Лучше взять какой то лог проги, и посмотреть как это логируется. У сисадминов посмотреть и проанализировать)
Это мало поможет т.к. UI сисадминов не волнует.
Для отработки простых (стандартных) ошибок я бы предложил такой вызов

Код
C++ (Qt)
bool SetLastError( const QString & txt, int errorID = ERR_ANY, int errorClass = ERR_CLASS_FATAL );
 
txt - все понятно
errorID - информация для вызывающего
errorClass - используется для установки типа алерта, иконки и.т.п. (общих атрибутов)

Это покроет многие (простые) запросы расчетной части, но не все. Для запросов типа Yes/No/Cancel - др. ф-ция. Согласен что обобщать больше неэффективно - проще делать "прокладку" для каждого случая которых остается относительно немного. В общем, работы хватает :) Заметим что слот/сигнал в таких случаях нехорош.