Использование БД SQL. Шаблон проектирования «Фасад» (Facade) [C++, Qt]

Статья состоит из трёх частей, в которых:

  1. описаны средства библиотеки Qt, предназначенные для работы с базами данных;
  2. рассмотрен паттерн проектирования «Facade». Описано каким образом его можно использовать для определения интерфейса базы данных;
  3. приведён и описан пример приложения, использующего базу данных и шаблон проектирования»Фасад».

Если Вы совсем не знаете что такое SQL — то перед чтением первой части статьи стоит посмотреть соответствующую литературу (ссылки внизу страницы). Первая и третья части рассчитаны на читателя, который уже знает что такое база данных, но хочет узнать как их использовать в своих программах, написанных на С++ с использованием библиотеки Qt.

Паттерн Facade является одним из наиболее часто используемых — многие применяют его, но не знают об этом. Графический интерфейс приложения из третьей части статьи создаётся с помощью Qt Designer, если Вы никогда им пользовались — ознакомьтесь с моей предыдущей статьей.

1 Библиотека Qt и базы данных

Некоторые товарищи уверены, что базы данных нужны только в крупных проектах, однако, это не так. Вы, например, можете использовать БД, уже в том случае, если необходимо сохранять какую-либо информацию между запусками программы (вместо .ini файлов и т.п.). Использование БД более безопасно и надёжно, чем написание своих велосипедов.

Библиотека Qt позволяет работать с различными базами данных, в том числе Oracle DB, MySQL, SQLite. В примерах статьи описана работа с SQLite, однако, от работы с другими базами она отличается разве что подключением (ну и сами базы, конечно, тоже отличаются, но Qt тут не причём). Если содержимое раздела будет не совсем понятно — не расстраивайтесь, будет третий раздел и будут примеры, которые помогут разобраться.

Для работы с SQL в Qt необходимо подключить соответствующий модуль, добавлением в файл проекта следующей строки:

QT += sql

Кроме того, необходимо подключить заголовочный файл для работы с SQL:

# include <QtSql/QtSql>

Перед началом работы с базой необходимо активизировать соответствующий драйвер, это делается статическим методом QSqlDatabase::addDatabase, который принимает строку, определяющую тип базы (QSQLITE/QMYSQL/…). Полученный методом QSqlDatabase::addDatabase экземпляр класса QSqlDatabase, используется для подключения к базе данных, при этом должно быть установлено имя базы (методом QSqlDatabase::setDatabaseName), а также, возможно хост (QSqlDatabase::setHostName), имя пользователя (QSqlDatabase::setUserName), пароль (QSqlDatabase::setPassword). Подключение к базе данных осуществляется вызовом метода QSqlDatabase::open.

После подключения, с базой возможно взаимодействовать посредством запросов. Передача запроса базе осуществляется методом exec класса QSqlQuery, экземпляр которого должен быть связан с нашей базой (если база одна, связывание произойдёт автоматически). Результатом запроса может быть набор строк, для навигации по которому используются такие методы класса QSqlQuery, как QSqlQuery::first, QSqlQuery::next.

Для получения информации о строке-результате запроса можно использовать экземпляр класса QSqlRecord, который инициализируется посредством вызова QSqlQuery::record. QSqlRecord позволяет получить индекс столбца по имени этого столбца, и чаще всего используется совместно с вызовом QSqlQuery::value для получения значения, хранимого столбцом строки-результата запроса.

Описанными выше средствами мы можем создать базу данных, передать ей запросы на создание таблиц, добавление и выборку данных, в принципе, этого достаточно для нормальной работы с БД. Результаты выборок удобно выводить в таблицу, мы можем сделать это руками, написав цикл с QSqlQuery::next(), однако в Qt есть более удобные, встроенные средства.

Для вывода данных таблицы на экран можно использовать класс QSqlTableModel, экземпляр которого необходимо связать с нашей таблицей базы данных методом QSqlTableModel::setTable. Вызов метода QSqlTableModel::select обновляет данные в модели (используется в случае, если данные в базе были изменены). За отображение таблиц в Qt отвечает класс QTableView, объект которого необходимо связать с моделью при помощи метода QTableView::setModel. Разместить объект QTableView на форме можно при помощи мыши, используя Qt Designer.

QTableView в связке с QSqlTableModel позволяет не только отображать данные базы данных, но и изменять их. Класс QSqlTableModel поддерживает несколько стратегий изменения, которые можно установить вызовом метода QSqlTableModel::setEditStrategy(). Работа с QSqlTableModel будет показана на примере в третьей части статьи.

QTableView может отображать не только таблицы базы данных, но и результаты отдельных выборок (выполнения запросов SELECT), для этого применяется класс QSqlQueryModel. Запрос SELECT передаётся в метод QSqlQueryModel::setQuery, а затем, модель отображается на QTableView вызовом QTableView::setModel.

2 Шаблон проектирования Facade («Фасад»)

Программа, чуть сложнее «hello world«, чаще всего, может быть разделена на подсистемы с фиксированным интерфейсом, т.е. набором функций, предоставляемым подсистемой. Паттерн Facade, как раз, и выступает в роли такого интерфейса, инкапсулируя данные подсистемы и задавая отношения между ее элементами.

Объект Фасад предоставляет упрощённый высокоуровневый интерфейс подсистемы, за счёт использования которого сокращаются зависимости между подсистемами.

Прочитав первую часть статьи, можно заметить, что для работы с базами данных в Qt используется множество объектов. Не очень хочется видеть все эти объекты в основной части программы, их нужно скрыть, для этого можно использовать Фасад. При этом, в программе появится экземпляр класса Facade, который будет предоставлять интерфейс для работы с подсистемой, мы не сможем больше обращаться к элементам подсистемы напрямую, поэтому интерфейс должен быть хорошо продуман. Схематично суть паттерна Facade показана на рис. 1.

рис. 1 использование паттерна Facade

рис. 1 использование паттерна Facade

Фасад — очень удобный и простой шаблон проектирования, который постоянно используется всеми без исключения программистами. Facade позволяет:

  • минимизировать зависимости между подсистемами;
  • минимизировать количество объектов, с которыми приходится взаимодействовать клиентам (упрощает работу с системой);
  • предоставить простой интерфейс к сложной системе, которого будет достаточно большинству других систем. Если кому-то не достаточно возможностей такого интерфейса — всегда можно напрямую использовать то, что спрятано за фасад (т.е. фасад ничего не сломал, однако лезть за него не желательно).

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

3 Пример использования баз данных на C++

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

Интерфейс описываемого приложения показан на рис. 2, он содержит:

  • 2 экземпляра QTableView (carView, seatView);
  • 4 экземпляра QLineedit (carID, carNSeats {количество мест в вагоне}, carNSeats {идентификатор вагона в таблице мест}, carNSeats {номер места});
  • 4 экземпляра QPushButton (carRem, carAdd, seatAdd, seatRem).
рис. 2 интерфейс приложения с БД

рис. 2 интерфейс приложения с БД

В основной части программы есть лишь 2 объекта — первый для отображения графического интерфейса, созданного в Qt Designer, второй — экземпляр класса Фасада. Исходный код заголовочного файла главного окна приведен в листинг 1.

#ifndef MAINUI_H
# define MAINUI_H
#include <QDialog>

namespace Ui {
  class MainUI;
}

class DBFacade;

class MainUI : public QDialog {
  Q_OBJECT
public:
  explicit MainUI(QWidget *parent = 0);
  ~MainUI();
public slots:
  void on_carAdd();
  void on_carRem();

  void on_seatAdd();
  void on_seatRem();
private:
  Ui::MainUI *m_ui;
  DBFacade* m_db;
};

#endif // MAINUI_H

Очевидно, класс DBFacade должен позволять добавлять и удалять информацию из базы. При обновлении информации, должно изменяться отображаемое содержимое таблиц, поэтому экземпляры QTableView должны быть связаны с моделями, инкапсулированными Фасадом.

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

#include "mainui.h"
#include "ui_mainui.h"
#include "dbfacade.h"

MainUI::MainUI(QWidget *parent)
  : QDialog(parent), m_ui(new Ui::MainUI) {
  m_ui->setupUi(this);

  m_db = new DBFacade(m_ui->carView, m_ui->seatView, this);

  connect(m_ui->carAdd, SIGNAL(clicked()), SLOT(on_carAdd()));
  connect(m_ui->carRem, SIGNAL(clicked()), SLOT(on_carRem()));

  connect(m_ui->seatAdd, SIGNAL(clicked()), SLOT(on_seatAdd()));
  connect(m_ui->seatRem, SIGNAL(clicked()), SLOT(on_seatRem()));
}

void MainUI::on_seatRem() {
  if (m_ui->seatCarID->text().isEmpty() || m_ui->seatNum->text().isEmpty())
    return;
  m_db->remSeat(m_ui->seatCarID->text(), m_ui->seatNum->text());
}

void MainUI::on_seatAdd() {
  if (m_ui->seatCarID->text().isEmpty() || m_ui->seatNum->text().isEmpty())
    return;
  m_db->addSeat(m_ui->seatNum->text(), m_ui->seatCarID->text());
}

void MainUI::on_carRem() {
  if (m_ui->carID->text().isEmpty())
    return;
  m_db->remCar(m_ui->carID->text());
}

void MainUI::on_carAdd() {
  if (m_ui->carID->text().isEmpty() || m_ui->carNSeats->text().isEmpty())
    return;
  m_db->addCar(m_ui->carID->text(), m_ui->carNSeats->text());
}

MainUI::~MainUI() {
  delete m_ui;
}

Видно, что конструктору класса DBFacade передаются указатели на QTableView, это делается для того, чтобы связать их с моделями таблиц БД, хранимыми за Фасадом.

Заголовочный файл класса Фасада приведен на листинг 3.

#ifndef DBFACADE_H
# define DBFACADE_H
# include <QtSql/QtSql>
# include <QObject>

class QTableView;
class QSqlTableModel;
class DBFacade : public QObject {
  Q_OBJECT
public:
  DBFacade(QTableView* cars, QTableView* seats, QObject *parent = 0);
  virtual ~DBFacade();

  void addCar(QString snum, QString nSeats);
          //!< добавляет вагон
  void remCar(QString snum);
          //!< удаляет вагон
  void addSeat(QString num, QString snCar);
          //!< добавляет место
  void remSeat(QString snCar, QString num);
          //!< удаляет место
protected:
  void exec(QString);
        //!< пытается выполнить запрос
  QString qs(QString);
        //!< выделяет строку одинарными кавычками

  QSqlDatabase m_db;
        //!< база данных
  QSqlQuery *m_query;
        //!< запрос к базе
  QSqlRecord m_rec;
        //!< строка таблицы (ответ на запрос)
  QSqlTableModel *m_carsModel;
        //!< модель таблицы автомобилей
  QSqlTableModel *m_seatsModel;
         //!< модель таблицы посадочных мест
};

#endif // DBFACADE_H

Как видно, фасад предоставляет интерфейс к базе данных, позволяющий добавлять и удалять данные в таблицы. При выполнении запросов DBFacade должен проверять их корректность, например, при добавлении посадочного места необходимо проверить его наличие (должен существовать соответствующий вагон, и вагон должен иметь достаточное количество сидений.

Исходный код реализации методов класса DBFacade приведен на листинг 4 без пояснений, если есть вопросы — задавайте в комментариях.

#include "dbfacade.h"
#include <QTableView>
/////////////////////////////////SEAT
void DBFacade::addSeat(QString num, QString carID) {
  exec(tr("SELECT * FROM cars WHERE id = ") + carID);
  m_query->first();
  if (m_query->value(m_rec.indexOf("nseat")).toString().toInt() < num.toInt())
    return;
  try {
    exec(tr("INSERT INTO seats (carID, nseat) VALUES(") + carID + "," + num + ")");
  }
  catch(...) {
    return;
  }
  m_seatsModel->select();
}

void DBFacade::remSeat(QString carID, QString num) {
  exec(tr("DELETE FROM seats WHERE carID = ") + carID + " AND nseat = " + num);
  m_seatsModel->select();
}

/////////////////////////////////CAR
void DBFacade::addCar(QString id, QString nSeats) {
  try {
    exec(tr("INSERT INTO cars (id, nseat) VALUES (")
      + id + ',' + nSeats + ")");
    m_carsModel->select();
  }
  catch(...) { return; }
}

void DBFacade::remCar(QString id) {
  exec(tr("DELETE FROM seats WHERE carID = ") + id);
  exec(tr("DELETE FROM cars WHERE id = ") + id);
  m_carsModel->select();
  m_seatsModel->select();
}

////////////////////////////////////////////
DBFacade::DBFacade(QTableView* cars, QTableView* seats, QObject *parent)
  : QObject(parent) {
  m_db = QSqlDatabase::addDatabase("QSQLITE");
  m_db.setDatabaseName("projectsDB");

  if (false == m_db.open())
    throw "can't open/create DB";

  m_query = new QSqlQuery(m_db);
  if (false == m_db.tables().contains("seats"))
    exec("CREATE TABLE seats ("  // таблица посадочных мест
      "carID INTEGER, " // серийный номер вагона
      "nseat INTEGER, " // номер посадочного места
      "PRIMARY KEY (carID, nseat)"
      ");"
    );
  if (false == m_db.tables().contains("cars"))
    exec("CREATE TABLE cars ("  // таблица вагонов
      "id INTEGER PRIMARY KEY, " // серийный номер вагона
      "nseat INTEGER" // количество посадочных мест
      ");"
    );

  m_carsModel = new QSqlTableModel(this, m_db);
  m_carsModel->setTable("cars");
  m_carsModel->select();
  m_carsModel->setEditStrategy(QSqlTableModel::OnFieldChange);
  cars->setModel(m_carsModel);

  m_seatsModel = new QSqlTableModel(this, m_db);
  m_seatsModel->setTable("seats");
  m_seatsModel->select();
  m_seatsModel->setEditStrategy(QSqlTableModel::OnFieldChange);
  seats->setModel(m_seatsModel);
}

DBFacade::~DBFacade() {
  delete m_query;
}

QString DBFacade::qs(QString str) {
  return "'" + str + "'";
}

void DBFacade::exec(QString str) {
  m_query->clear();
  if (false == m_query->exec(str))
    throw tr("DBFacade Error: can't exec : ") + str;
  m_rec = m_query->record();
}

Исходный код примера как всегда можно скачать: Работа с БД[Qt] (исходный код).

Источники, ссылки

  1. Книги раздела «проектирование» (в предложенных книгах более подробно описано про Facade и другие шаблоны проектирования);
  2. Интерактивный учебник SQL. Режим доступа: http://www.sql-tutorial.ru/en/content.html. Очень интересный ресурс. Содержит много примеров и заданий. Интерактивность заключается в вовзможности выполнения запросов с помощью специального сервиса;

14 thoughts on “Использование БД SQL. Шаблон проектирования «Фасад» (Facade) [C++, Qt]

  1. releyshic

    ТЫ такой классный чувак, раз написал такую хорошую статью!
    Нормальный пример использования БД SQL, которых очень мало в нете. Можно теперь простенькие программы на основе твоей писать! И обучаться дальше
    Только есть вопрос: этот Дизайнер интерфейса не более тормозной ,чем стандартный в студии? или сравнимо? Получится ли написать с его помощью не тормозной графический редактор, который будут взаимодействовать с БД?

    1. admin Post author

      Редактор в студии… MFC который? — я им почти не пользовался.
      Что значит «тормозной дизайнер» я не знаю.
      Сам дизайнер не много ресурсов от компьютера требует. Код генерируется тоже нормальный. На эффективность кода, ИМХО, дизайнер вообще не влияет (издержки минимальны).

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

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

  2. Bogdan

    Спасибо. Очень полезная и интересная статья. Жаль что раньше не наткнулся.

    Если у меня в проекте таких форм много, значит в классе каждой формы создавать дополнительное приватное поле ?
    DBFacade* m_db;
    Это не очень хорошо выглядит. Получится, что объект фасада в каждой форме будет иметь излишние методы и подключения к базе.

    Объясните или поправьте меня, пожалуйста.

    Добавлено: Можно для каждой формы создавать свой фасад, или создать абстрактный класс фасада, определить в нем все необходимое и унаследовать от него классы для каждой формы, где будут переопределены специфические методы. Так правильно делать?
    Но тогда остается вопрос о подключении к БД. Или это нормально в каждом классе, который работает с БД, создавать новое подключение? QSqlDatabase вроде как Singleton.

    1. admin Post author

      Все что делает шаблон проектирования «фасад» в этом случае — предоставляет простой интерфейс к базе данных, упакованный в один класс, т.е. объект фасада (DBFacade) используется как обертка над базой. Каждый класс, который хочет работать с базой должен дело это посредством обращения к объекту фасада.

      У вас есть несколько форм (объекта), которые должны обращаться к БД. Возможны три варианта сделать это:

      1. каждый метод (функция-член) класса, обращающийся к базе должен принимать указатель на объект базы данных. Это один из самых правильных вариантов, но он не всегда удобен, т.к. указатель придется «тянуть» через все объекты приложения. С другой стороны, если этот вариант оказывается неудобен, значит объекты, расположенные «далеко» друг от друга связываются с одной и той же базой — скорее всего с архитектурой приложения что-то не так;
      2. указатель на фасад можно хранить внутри каждого класса, у которого есть методы, работающие с базой. Этот вариант упростит вызов функций (им не надо передавать базу в качестве аргумента), но указатель на базу все равно будет надо передавать по всем формам. Я советую Вам пользоваться этим вариантом — конструкторы классов, работающих с базой принимают указатель на DBFacade в качестве аргумента и сохраняют его внутри. Все функции, которым нужно обратиться к базе — используют этот указатель;
      3. фасад можно сделать глобальным объектом, для этого можно применить шаблон проектирования Singleton. Это удобный вариант, т.к. теперь не надо передавать указатель через всю программу, но у вас появляется глобальный объект со всеми вытекающими последствиями (прочитайте статью по ссылке).

      «Излишних методов подключения к базе» у вас не будет ни при одном варианте. Подключением к базе занимается объект-фасад. Класс формы просто принимает указатель:

      class Form {
      public:
        Form(DBFacade *db) : m_db(db) {
        }
        string database_call() {
          return m_db->get_smallest_car(); // interface db example
        }
      private:
        DBFacade *m_db;
      }
    2. admin Post author

      Для каждой формы создать свой фасад не надо.
      Новый объект фасада создается тогда, когда появляется новая база данных с новым интерфейсом.

      Наследовать форму от Фасада нельзя. Это нарушает все принципы объектно-ориентированного программирования, в частности принцип подстановки Лисков (LSP).

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

      Закрытое наследование реализует отношение «реализуется посредством», т.е. тоже самое, что и агрегация (которую я предложил использовать выше). Но агрегация — это более слабая связь, чем наследование, стоит использовать именно ее если нет каких-либо особых причин.

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

      QSqlDatabase вроде как Singleton.

      Как он может быть Singleton-ом? — разве я могу только одну базу данных в программе использовать?

      1. Bogdan

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

        Но я уже понимаю, что это лишняя робота и нарушает данный паттерн проектирования.

        Наконец-то я разобрался. Спасибо за столь развернутый и быстрый ответ. Очень Вам благодарен.

        Как он может быть Singleton-ом? – разве я могу только одну базу данных в программе использовать?
        Мда… Не подумал :)

        1. admin Post author

          Абстрактный класс фасада (в целом) не нужен, т.к. сам по себе фасад не содержит интерфейса.
          Создавать фасад для каждой формы отдельно неправильно, т.к. он должен зависеть только от базы данных. Форма использует фасад и если при этом сам фасад будет зависеть от формы — будет нарушен принцип инверсии зависимостей (Dependency inversion principle). В общем случае DIP чуть сложнее и нарушить его проще — я напишу про него и другие принципы SOLID подробнее в одной из следующих статей.

          С другой стороны, если ваша программа будет использовать несколько баз данных — то выделить базовый класс фасада базы данных может быть имеет смысл. Например, я делал так в игрушке с ежиком под Android — там были база данных уровней и база данных настроек приложения. У них был общий базовый класс, но интерфейсом его назвать трудно. Исходный код можно посмотреть (ссылка на репозиторий где-то на блоге). На самом деле, советую посмотреть тот пример — он лучше чем в этой статье написан.

          1. Bogdan

            Понял. Хорошо. Спасибо что так доходчиво объясняете. Походу, Ваш блог еще не раз будет меня выручать.
            А пока-что, впереди вся ночь переделывания архитектуры проекта…) Это послужит мне хорошим уроком.

  3. avovana

    Вроде публиковал, попробую еще раз :)

    Спасибо за интересную статью, за раздел, за весь сайт!
    Буду по-тихоньку изучать, в т.ч. и темы на форуме.

    Хотел бы спросить по поводу шаблона.
    Работаю с Qt’вской Моделью-Представлением. У созданной модели помимо основных переопределенных мною функций добавились еще. К примеру, функции по извлечению и сохранению данных в XML. И мне не хочется, чтобы эти функции присутствовали в самой модели. Хочется держать модель девственно чистой.
    Хочется вынести эти функции для работы с XML куда-нибудь. Но им же нужен доступ к внутренним данным модели. Ну… сделать их, к примеру friend. Но, все-равно, как-то не очень. Хочется что-то вроде объекта, отвечающего за это… Может со статическими функциями. Подскажите пожалуйста что можно сделать?

    1. Васильев Владимир Сергеевич Post author

      Возможно стоит использовать паттерны типа Strategy. В общем случае, если на ваш класс возлагается слишком много обязанностей (вот как вы говорите — на модель возлагается работа с XML) — то лишний функционал выносится во вспомогательный класс. В модель при этом помещается экземпляр нового класса. Я тут писал статью про принципы SOLID, вот в ней кроме описания самих принципов вроде бы приводил примеры того, как можно бороться с нарушениями принципов. Посмотрите, если останутся вопросы — пишите.

  4. Desmond

    Здравствуйте, отличная статья.
    Я заметил, что подключение к удаленным серверам (не локалхост) занимает некоторое время и из-за этого зависает программа, т.к. подключение идет в основном потоке.
    Если я правильно помню, то работа с потоками(один из способов) осуществляется с помощью moveToThread(..). Но в статьях по этой теме я видел примеры, где используется сигнально-слотный метод обмена информацией. Можно ли здесь реализовать такое? Как будет обновляться таблица QTableView? Законно ли выкинуть весь класс работы с БД в отдельный поток, чтобы программа не зависала?

    1. Васильев Владимир Сергеевич Post author

      Думаю да, можно всю работу с БД выкинуть в отдельный поток. У нас есть примеры на moveToThread в статье про многопоточный сервер чата: https://pro-prof.com/archives/1390 и разработку парсера биржи фриланса: https://pro-prof.com/archives/1034. В данном случае, вероятно в отдельный поток надо выкинуть объект типа DBFacade. Спасибо Вам за замечание.

      1. Desmond

        Здравствуйте ещё раз.
        Я решил перекинуть объект класса DBFacade в другой поток с помощью moveToThread.
        Всё отлично работает. Но у меня вопрос. Если я буду вызывать с основного потока какой-то метод (допустим, получить список городов из БД) класса DBFacade, то не будет проблем? У меня вроде как нету ошибок. Но я видел в статьях, что используют сигналы/слоты для обмена информацией. Обязательно только ими пользоваться?

        1. Васильев Владимир Сергеевич Post author

          Извиняюсь, что долго не отвечал. Ваш вопрос пришелся на пик активности студентов и затерялся.
          Если вы вызываете метод — то это равносильно обычному вызову функции — он выполняется в том потоке, из которого вызывается. Ну а если другой поток в это же время вызовет другой метод, работающий с этими же данными — будет состояние гонок.
          Если же в объект приходят сигналы — то они встают в очередь и обрабатываются последовательно (про это можно прочитать в отдельной статье) — гарантируется что гонок потоков не будет. Это хороший и удобный механизм. Пока один поток ковыряет очередь сигналов и что-то там делает с БД — другой делает что-то, например, с сокетом.

Добавить комментарий