Есть у меня один проект долгострой, в котором использую Yandex Dictionary Api. В процессе разработки решил поделиться опытом создания асинхронного интерфейса к интернет-сервису.
Если у вас есть интерес, как реализовать такой клиент с помощью Qt C++, то этот пост для вас.
Я не стал заострять внимания на тех моментах Qt, которые и так хорошо описаны. В статье я попытался раскрыть, как создавать асинхронные классы в Qt на базе конкретного примера.
Пример программы
Программа посылает на сервер Yandex Dictioanary Api запросы на перевод слов и затем, по мере поступления ответов от сервиса, выводит их на экран.
#include "Precompiled.h"
#include <QtYandexApi/QtYandexApi.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QtYandexDictionary yandexDictionary(QtYandexApi::getYandexKeyFromFile("dictKey"));
QObject::connect(&yandexDictionary, &QtYandexDictionary::translated,
[](const QtYaWordTranslation& wordTranslation)
{
if (wordTranslation.isError())
qDebug() << wordTranslation.errorString();
else {
QtYaWord wordForTranslation = wordTranslation.wordForTranslation();
QtYaTranslatedWord translatedWord = wordTranslation.translatedWord();
qDebug() << "n***************";
qDebug() << "Word: " << wordForTranslation.wordName();
qDebug() << "Direction: " << wordForTranslation.fromLanguage() << "-" << wordForTranslation.toLanguage();
qDebug() << "Main translation: " << translatedWord.mainTranslation();
qDebug() << "Synonyms: " << translatedWord.synonyms();
qDebug() << "Examples: ";
for (const auto& example : translatedWord.examples()) {
qDebug() << example.first << "-" << example.second;
}
}
});
QStringList russianWords, englishWords;
russianWords << "дом" << "время" << "легенда" << "ключ" << "клавиатура" << "монитор" << "случай" << "один" << "два" << "три" << "четыре" << "пять" << "шесть";
englishWords << "home" << "time" << "legend" << "key" << "keyboard" << "monitor" << "infection" << "one" << "two" << "three" << "four" << "five" << "success";
for (const QString& word : russianWords) {
yandexDictionary.translate(QtYaWord(word, "ru", "en"));
}
for (const QString& word : englishWords) {
yandexDictionary.translate(QtYaWord(word, "en", "ru"));
}
return a.exec();
}
Результат работы программы:
***************
Word: «время»
Direction: «ru» — «en»
Main translation: «time»
Synonyms: («while»)
Examples:
«тромбопластиновое время» — «thromboplastin time»
«принять промежуток времени» — «take a while»***************
Word: «клавиатура»
Direction: «ru» — «en»
Main translation: «keyboard»
Synonyms: («keypad», «pad»)
Examples:
«экранная клавиатура» — «onscreen keyboard»
«ЖКИ-клавиатура» — «LCD keypad»
«цифровая клавиатура» — «numeric pad»***************
Word: «ключ»
Direction: «ru» — «en»
Main translation: «key»
Synonyms: («turnkey», «clue», «wrench», «dongle»)
Examples:
«деблокировочный ключ» — «unblocking key»
«решение под ключ» — «turnkey solution»
«иметь никакой ключ» — «have no clue»
«шестигранный ключ» — «hexagonal wrench»***************
Word: «монитор»
Direction: «ru» — «en»
Main translation: «monitor»
Synonyms: ()
Examples:
«ЭЛТ-монитор» — «CRT monitor»***************
Word: «четыре»
Direction: «ru» — «en»
Main translation: «four»
Synonyms: ()
Examples:
«двадцать четыре часа» — «twenty four hours»***************
Word: «один»
Direction: «ru» — «en»
Main translation: «one»
Synonyms: ()
Examples:
«одна иота» — «one jot»***************
Word: «два»
Direction: «ru» — «en»
Main translation: «two»
Synonyms: ()
Examples:
«две несмешивающиеся жидкости» — «two immiscible liquids»***************
Word: «легенда»
Direction: «ru» — «en»
Main translation: «legend»
Synonyms: («myth»)
Examples:
«древнегреческая легенда» — «ancient Greek legend»
«городская легенда» — «urban myth»***************
Word: «три»
Direction: «ru» — «en»
Main translation: «three»
Synonyms: ()
Examples:
«три мушкетера» — «three musketeers»***************
Word: «случай»
Direction: «ru» — «en»
Main translation: «case»
Synonyms: («event», «occasion», «chance», «occurrence»)
Examples:
«невзвешенный случай» — «unweighted case»
«несчастный случай» — «unfortunate event»
«тот редкий случай» — «that rare occasion»
«слепой случай» — «blind chance»
«редкий случай» — «rare occurrence»***************
Word: «пять»
Direction: «ru» — «en»
Main translation: «five»
Synonyms: ()
Examples:
«семьдесят пять лет» — «seventy five year»***************
Word: «home»
Direction: «en» — «ru»
Main translation: «дом»
Synonyms: ()
Examples:
«ancestral home» — «отчий дом»***************
Word: «шесть»
Direction: «ru» — «en»
Main translation: «six»
Synonyms: ()
Examples:
«шесть сигм» — «six Sigma»***************
Word: «time»
Direction: «en» — «ru»
Main translation: «время»
Synonyms: («раз», «тайм»)
Examples:
«thromboplastin time» — «тромбопластиновое время»
«umpteenth time» — «энный раз»
«second time» — «второй тайм»***************
Word: «key»
Direction: «en» — «ru»
Main translation: «ключевой»
Synonyms: ()
Examples:
«key informants» — «ключевые информанты»***************
Word: «monitor»
Direction: «en» — «ru»
Main translation: «монитор»
Synonyms: («видеомонитор»)
Examples:
«LCD monitor» — «жидкокристаллический монитор»***************
Word: «infection»
Direction: «en» — «ru»
Main translation: «инфекция»
Synonyms: («зараза»)
Examples:
«cholangiogenic infection» — «холангиогенная инфекция»
«this infection» — «эта зараза»***************
Word: «one»
Direction: «en» — «ru»
Main translation: «один»
Synonyms: ()
Examples:
«one jot» — «одна иота»***************
Word: «keyboard»
Direction: «en» — «ru»
Main translation: «клавиатура»
Synonyms: ()
Examples:
«QWERTY keyboard» — «клавиатура QWERTY»***************
Word: «three»
Direction: «en» — «ru»
Main translation: «три»
Synonyms: ()
Examples:
«three musketeers» — «три мушкетера»***************
Word: «four»
Direction: «en» — «ru»
Main translation: «четыре»
Synonyms: ()
Examples:
«twenty-four» — «двадцать четыре»***************
Word: «two»
Direction: «en» — «ru»
Main translation: «два»
Synonyms: ()
Examples:
«two immiscible liquids» — «две несмешивающиеся жидкости»***************
Word: «five»
Direction: «en» — «ru»
Main translation: «пять»
Synonyms: ()
Examples:
«ninety-five» — «девяносто пять»***************
Word: «legend»
Direction: «en» — «ru»
Main translation: «легенда»
Synonyms: («предание», «сказание», «поверье»)
Examples:
«ancient Greek legend» — «древнегреческая легенда»
«ancient legend» — «древнее предание»
«epic legends» — «эпические сказания»
«folk legend» — «народное поверье»***************
Word: «success»
Direction: «en» — «ru»
Main translation: «успех»
Synonyms: («успешность», «удача»)
Examples:
«resounding success» — «оглушительный успех»
«financial success» — «финансовая успешность»
«bring success» — «приносить удачу»***************
Word: «дом»
Direction: «ru» — «en»
Main translation: «house»
Synonyms: ()
Examples:
«дом Стенбока» — «Stenbock house»
Код этого примера, а так же реализация самого клиента — bitbucket.org/milovidov/qtyandexapi.
Реализация клиента
В данном разделе я постараюсь осветить некоторые моменты, специфичные для Qt и позволяющие легко реализовывать асинхронные клиенты.
Интерфейс класса QtYandexDictionary выглядит следующим образом:
#ifndef QTYANDEXDICTIONARY_H
#define QTYANDEXDICTIONARY_H
#include <QObject>
#include "qtyandexapi_global.h"
#include "QtYaWord.h"
#include "QtYaWordTranslation.h"
#include "QtYaLanguages.h"
class QNetworkAccessManager;
class QTYANDEXAPISHARED_EXPORT QtYandexDictionary : public QObject
{
Q_OBJECT
public:
explicit QtYandexDictionary(const QString& yandexApiKey, QObject *pParent = 0);
void translate(const QtYaWord& word);
void getLanguages();
signals:
void translated(const QtYaWordTranslation& translation) const;
void languagesGot(const QtYaLanguages& langs) const;
private:
QString m_yandexApiKey;
QNetworkAccessManager* m_pNetworkAccessManager;
};
#endif // QTYANDEXDICTIONARY_H
Методы translate и getLanguages асинхронные. Они формируют и отправляют соответствующие запросы к Yandex Dictionary Api и возвращают управление потоку из которого были вызваны.
Сигналы translated и languagesGot эмитятся после получения какого либо результата от Yandex Dictionary Api, либо при невозможности подключиться к этому сервису. В аргументах этих сигналов содержится информация полученная от Яндекса или же текст ошибки при невозможности подключиться к нему.
Если посмотреть на код функции translate класса QtYandexDictionary, то она выглядит следующим образом:
void QtYandexDictionary::translate(const QtYaWord &word)
{
QtYaTranslationGettor* pYandexTranslationGettor = new QtYaTranslationGettor(m_yandexApiKey, word, m_pNetworkAccessManager, this);
connect(pYandexTranslationGettor, &QtYaTranslationGettor::translated, this, &QtYandexDictionary::translated);
connect(pYandexTranslationGettor, &QtYaTranslationGettor::translated, pYandexTranslationGettor, &QtYaTranslationGettor::deleteLater);
}
Здесь мы видим, что для каждого слова переданного в функцию translate создается уникальный экземпляр класса QtYaTranslationGettor. Этот класс непосредственно занимается переводом слов через Yandex Dictionary Api и так же эмитит сигнал translated. Этот же сигнал, без каких либо изменений пробрасывается наверх к QtYandexDictionary. После эмита этого сигнала экземпляр класса QtYaTranslationGettor удаляется за счет коннекта к его же слоту deleteLater.
Как видим, никаких очередей запросов, никакого контроля создаваемых объектов. Каждый объект имеет свое слово для перевода и сам удаляется после завершения своей работы и отправки результата.
Метода getLanguages реализован аналогично:
void QtYandexDictionary::getLanguages()
{
QtYandexLanguagesGettor* pYandexLanguagesGettor = new QtYandexLanguagesGettor(m_yandexApiKey, m_pNetworkAccessManager, this);
connect(pYandexLanguagesGettor, &QtYandexLanguagesGettor::languagesGot, this, &QtYandexDictionary::languagesGot);
connect(pYandexLanguagesGettor, &QtYandexLanguagesGettor::languagesGot, pYandexLanguagesGettor, &QtYandexLanguagesGettor::deleteLater);
}
Реализация классов QtYaTranslationGettor и QtYandexLanguagesGettor содержит в себе стандартную практику работы с классом QNetworkAccessManager и работу с JSON в Qt5. Поэтому не буду заострять внимания на деталях их реализации.
Тестирование клиента
В данном разделе я покажу, как можно тестировать асинхронные Qt классы.
Пример тестирования асинхронной функции translate выглядит так:
void TQtYandexApi::testTranslation()
{
const QString rusHome = QString("Home");
QSignalSpy signalSpy(m_pYandexDictionary, SIGNAL(translated(QtYaWordTranslation)));
QMetaObject::Connection connection = connect(m_pYandexDictionary, &QtYandexDictionary::translated,
[&](const QtYaWordTranslation& translation)
{
QVERIFY2(translation.isError() == false, translation.errorString().toStdString().c_str());
QVERIFY(translation.wordForTranslation().wordName() == rusHome);
QVERIFY(translation.wordForTranslation().fromLanguage() == "en");
QVERIFY(translation.wordForTranslation().toLanguage() == "ru");
QVERIFY(translation.translatedWord().mainTranslation().toLower() == QString::fromUtf8("дом"));
});
m_pYandexDictionary->translate(QtYaWord(rusHome, "en", "ru"));
END_ASYNC_FUNCTION(signalSpy, connection)
}
В начале создаем слово для перевода с английского на русский — переменная rusHome.
Затем создаем signalSpy — это объект будет ждать сигнала от m_pYandexDictionary.
Далее создается обработчик сигнала translated в виде лямбды выражения. В ней проверяется правильность перевода слова.
После этого отправляется запрос на перевод и вызывается макрос END_ASYNC_FUNCTION.
Код этого макроса:
#define END_ASYNC_FUNCTION(signalSpy, connection)
QVERIFY(signalSpy.wait());
QObject::disconnect(connection);
Сначала ждем прихода сигнала. По умолчанию время ожидания сигнала 5 секунд. Если он придет, то начнет выполняться код обработчика, который мы задали в лямбда выражении ранее. Если нет, то тест зафейлится на этой строке.
После отключаем коннект с лямбда выражением, т.к. иначе он сохраниться и после выхода из функции.
Выводы
В статье я привел простой способ создания асинхронных функций и раскрыл способ их тестирования.
Автор: 1vertus1