Wczytywanie danych do połączenia z bazą do programu

0

Mam jedno pytanie - ale zadam je na samym końcu.
Mam klasę która zawiera pola do łączenia się z bazą danych i wygląda ona tak

AccessToDB::AccessToDB()
{
    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setHostName("127.0.0.1");
    db.setPort(0);
    db.setDatabaseName("dbSpIT.db");
    db.setUserName("");
    db.setPassword("");
}

Opracowałem okno z ustawieniami programu, w których można zapisać wymagane ustawienia aby można było połączyć się z bazą danych i ustawienia zapisywane są do pliku JSON. Okno wygląda tak

screenshot-20221027202905.png

plik JSON, ma taką strukturę

"network": {
  "connect": "Komputer lokalny",
  "database-name": "dbSpIT.db",
  "database-type": "SQLite",
  "ipaddr": "127.0.0.1",
  "login": "login",
  "password": "73899d2adaad774417b0208da85162b61c8dbdf79bb0f7108c2686b93721d1f4",
  "port": "11"
}

wyszstko się zapisuje dobrze i działa dobrze, z tym, że mam pytanie związane z tym zagadnieniem.

W jaki sposób wczytać ustawienia z pliku JSON do tej klasy ? Bynajmniej nie chodzi mi o obsługę wczytywania plików JSON ale chodzi mi o to w jaki sposób przekazać wczytane opcje do klasy ? Czy trzeba to zrobić w którymś z konstruktorów ?

1

Jest tak z pięćset rozwiązań… Tak, parametry konstruktora są jednym z nich — prawdopodobnie najsensowniejszym, ale bynajmniej nie jedynym.

0

aa i jeszcze jedno tak przy okazji - czy da radę ustawić login i hasło na bazę w SQLite ?

1

Komentuj w komentarzach, pytaj w odpowiedziach:

aa i jeszcze jedno tak przy okazji - czy da radę ustawić login i hasło na bazę w SQLite ?

Nie.

0
Althorion napisał(a):

Jest tak z pięćset rozwiązań… Tak, parametry konstruktora są jednym z nich — prawdopodobnie najsensowniejszym, ale bynajmniej nie jedynym.

a podasz kilka z nich ? Może zdecyduję się na coś innego ?

0

Trochę się boję podawać, bo jeszcze faktycznie możesz spełnić swoją groźbę i wybrać co innego…

Ale, no, tu nie ma żadnej magii — normalne zmienne wewnętrzne obiektu. Możesz mieć funkcję inicjalizującą, możesz czytać ze zmiennych globalnych poza obiektem, możesz zgłosić swoją kandydaturę do Underhanded C++ Contest i sobie np. przeciążyć operator<< do ustawiania tego… No wszystko możesz.

Większość z tych rozwiązań będzie miała sens tylko w wyjątkowych okolicznościach¹, ale oczywiście istnieją i jest ich od groma.


¹ Typowe wyjątkowe okoliczności — płacą Ci od linijki kodu i/lub nienawidzisz swoich współpracowników.

0
Althorion napisał(a):

Trochę się boję podawać, bo jeszcze faktycznie możesz spełnić swoją groźbę i wybrać co innego…

a czemu się boisz ? To jakaś niebezpieczna wiedza ?

Co do reszty, to chyba zostanę przy konstruktorze. Tylko zrobię to w ten sposób, że - konstruktor MainWindow wywoła konstruktora okna WindowSettings czyli okna, które odpowiada za zapisywanie opcji do pliku json, jedna z funkcji okna WidowSettings zwróci wskaźnik do wczytanego pliku json i wczytane parametry przekażę klasie AccessToDB która odpowiada za połączenie z bazą danych i wszystko to się odbędzie w MainWindow

Dobrze wymyśliłem ?

2
zkubinski napisał(a):
Althorion napisał(a):

Trochę się boję podawać, bo jeszcze faktycznie możesz spełnić swoją groźbę i wybrać co innego…

a czemu się boisz ? To jakaś niebezpieczna wiedza ?

Co do reszty, to chyba zostanę przy konstruktorze. Tylko zrobię to w ten sposób, że - konstruktor MainWindow wywoła konstruktora okna WindowSettings czyli okna, które odpowiada za zapisywanie opcji do pliku json, jedna z funkcji okna WidowSettings zwróci wskaźnik do wczytanego pliku json i wczytane parametry przekażę klasie AccessToDB która odpowiada za połączenie z bazą danych i wszystko to się odbędzie w MainWindow

Dobrze wymyśliłem ?

Jesteś dobry przykładem poglądu, że wiedza jest niebezpieczna. Ale zachowujesz się "bezpiecznie"

  1. Scenariusz wywołań klas, jaki podałeś, jest zaprzeczeniem jakiegokolwiek programowania obiektowego, nawet naiwnego tego z początków OOP z lat 1990ch.
    Wyobraż sobie, że za rok, na podstawie tej samej bazy danych, robisz aplikację command line, która z bazy wypluwa jakiś wynik select'u. Pomedytuj na tym zdaniem.

  2. Rodzi sie pytanie, jak inicjujesz danymi zewnętrznymi pola innych klas, przecieź po tylu latach prawdopodobnie robiłeś to setki razy

2

@zkubinski:

a czemu się boisz ? To jakaś niebezpieczna wiedza ?

Każda niepełna wiedza jest niebezpieczna.

Dobrze wymyśliłem ?

Brzmi karkołomnie — Twoje funkcje zaczynają robić parę rzeczy na raz. Spróbowałbym pomyśleć o architekturze niejako „od tyłu” — co potrzebuje się połączyć z tą bazą? W jakim momencie zacznie istnieć jako obiekt? Skąd wtedy może wziąć wszystkie potrzebne informacje? Skąd one się tam znajdą? Itd.

Konstruktor jest bardzo naturalnym rozwiązaniem, jeśli klasa potrzebuje coś wiedzieć, mieć wstrzyknięte pewne zależności, od samego początku swego istnienia. Ale może nie chcesz, żeby tak było — istnienie nie w pełni zainicjowanego obiekty nie jest u Ciebie błędogennym zagrożeniem wymuszającym niepotrzebnie defensywne programowanie, a pożądaną własnością¹. Musisz się zastanowić, co zrobić w sytuacji, gdy dane potrzebne klasie realizującej połączenie z bazą będą w jakiś sposób trefne — czy rzucający wyjątek konstruktor jest najwygodniejszy, czy może będziesz wolał fabrykę; albo może konstruktor ma się zawsze powieść, i tylko obiekt będzie zwracał jakieś kody błędów², jak coś nie udzie, itd.

Ogólnie jesteś na etapie, w którym możesz sobie zaoszczędzić kilku godzin myślenia kilkoma tygodniami programowania.


¹ Jest to jeden z tych punktów, gdzie można się wpakować w problemy złym rozwiązaniem architektonicznym.
² W jaki sposób? std::optional, std::variant, „specjalne” wartości wyjściowe jak za króla Ćwieczka?

0

@ZrobieDobrze: & @Althorion - to jak to poskładać normalnie w całość ?

1

Zależy, rzecz jasna, od tego, czym jest ta całość. Podałem Ci możliwą strategię, oraz parę bardziej oczywistych miejsc, które mogą generować problemy. Jest ich pewnie dużo więcej. Tak jak pisałem, jesteś w dobrym momencie, żeby siąść sobie z kartką papieru i ołówkiem, i wymyślić, jak chcesz mieć zorganizowany swój program. Jaki jest przepływ danych, co za co odpowiada, jak reagować w sytuacjach nadzwyczajnych, itd. Popisz sobie, co za co odpowiada; zdecyduj, czy nie próbujesz zrobić za wiele rzeczy jednym typem; popatrz jak to wszystko od siebie nawzajem zależy…

Idealnie tego za pierwszym razem na pewno nie zrobisz — ludzie tego nie robią idealnie po latach doświadczenia — ale to zawsze jakaś praktyka. A i nawet nie idealne rozwiązanie Ci powinno oszczędzić czas i nerwy.

2

Okienka nie powinno obchodzić czy dane pochodzą z JSON, XML, YAML czy jeszcze czegoś innego.
Stwórz prostą strukturę do transportu danych między widgetem a 'mięsem' apki i wsio.

struct DbConnectionSettings
{
  QString name, host, login, password, ip;
  int port;

  static DbConnectionSettings getDefaults();
};

class DbConnectionSettingsWidget : public QWidget
{
  public:
    explicit DbConnectionSettingsWidget(QWidget* parent = nullptr);
    DbConnectionSettingsWidget(DbConnectionSettings const& dbSettings, QWidget* parent = nullptr);

    DbConnectionSettings getSettings();
    DbConnectionSettings const& getSettings() const;

  private:
    DbConnectionSettings connSettings;

    /* ... signals & slots, widgets, etc. */
};

Można to też zrobić za pomocą Model/View :)

0

Spróbuję przedstawić sprawę od strony kodu jak to u mnie wygląda - nie chcę zawalać wątku długim kodem ale zgrubnie opiszę temat - dlaczego to piszę ? Bo może ktoś mi z was podpowie co i gdzie wywołać. Ogólnie wiem jak to zrobić aby to działało, tylko robię tą wrzutkę tutaj, bo chcę to zrobić poprawnie, może ktoś z was wyprowadzi mnie z błędu.

wszystko zaczyna się w klasie AllSettings - ta klasa zajmuje się utworzeniem całej struktury JSON i wygląda jak poniżej - dla uproszczenia nie podaję szczegółów implementacyjnych

class AllSettings : public QObject
{
    Q_OBJECT

public:
    AllSettings() = default;
    AllSettings(const AllSettings &_settings) = delete;
    AllSettings &operator=(const AllSettings &_settings) = delete;

    QJsonObject &showSettings();

public slots:
    void createNetworkSettings(QJsonObject &networkSettings);
    void createDataSettings(QJsonObject &dataSettings);
    void UpdateSettings();

private:
    QJsonObject *crteNetworkSettings, *crteDataSettings, allSettings;

signals:
    void _settings(QJsonObject &allSettings);
};

następnie wszystko z powyższej klasy wędruje do klasy SettingsFile - klasa ta zajmuje się zapisem do pliku całego utworzonego powyżej json-a

class SettingsFile : public QObject
{
    Q_OBJECT

public:
    SettingsFile() = default;

    QJsonObject *fileSettings();
    void saveSettings();
    QJsonObject readSettings();

public slots:
    void setFileSettingsToSave(QJsonObject &settingsToSave);
    void fileSettingsToRead();

private:
    QJsonObject *settingsToSave;
    QFile settingsToFile;
    QString department, connection, ipAddress, login, password;
    QJsonObject objectJson;
    QMessageBox message;

signals:
//    void DataReadFromFile(QJsonObject &dataFromJsonFile);
};

potem tworzę okno podrzędne w stosunku do MainWindow za pomocą klasy WindowSettings - to jest to okno, które widać w pierwszym poście i zadaniem tego okna jest zebranie wszystkich danych od usera i przekazanie ich do wszystkich obu klas tj. AllSettings & SettingsFile - klasa okna podrzędnego wygląda jak niżej

class WindowSettings : public QDialog
{
    Q_OBJECT

public:
    WindowSettings(QWidget *parent = nullptr);
    ~WindowSettings();

private:
    QHBoxLayout *layoutForLabel, *layoutForButton;
    QLabel *label1, *label2;
    QFont *fontOptions;
    QPalette *fontColor, *fontColor2;
    QBrush *brushFont, *brushFont2;

    QWidget *mainWidget;
    QVBoxLayout *mainLayout;
    QStackedWidget *stackedWidget;
    QListWidget *optionList;
    QMenu *pbClearMenu;
    QPushButton *pbSave, *pbApply, *pbShowSettings, *pbPopupOptions, *pbExit;

    QHBoxLayout *layoutForStackedWidget;

    Page1 *myPage1;
    Page2 *myPage2;
    SettingsPreview *settingsPreview;

    AllSettings mySettings;
    SettingsFile mySettingsFile;

    QMessageBox messageError;

//    void StateReadJsonFile(const int &state);
    void dataFromJsonFile(const QJsonObject &data);

private slots:
    void clearEdit();
    void readSettings();
};

i teraz gdzieś w MainWindow odpalam to okienko za pomocą takiej funkcji

void MainWindow::createActionMenuTools()
{
    actionMenuTools = new QAction(this);
    actionMenuTools->setText(QString(tr("Ustawienia")));
    actionMenuTools->setIcon(QIcon(":icons/settings.png"));

    windowSetting = new WindowSettings(this); //tutaj tworzę okno z ustawieniami

    QObject::connect(actionMenuTools, &QAction::triggered, windowSetting, &QDialog::exec);
}

i ogólnie ja chcę zrobić to w ten sposób, że jak wywołuje się konstruktor klasy WindowSettings w klasie MainWindow - to przecież zanim on zbuduje całe to okno, to wcześniej odpali mi konstruktory klas AllSettings i SettingsFile czyli dane z pliku zostaną wczytane, zanim zbuduje mi się okno klasy WindowSettings bo na tą okoliczność napisałem funkcję w klasie SettingsFile która zwraca wskaźnik do całego wczytanego pliku json gdzieś w pamięci i ten fakt właśnie chcę wykorzystać do kilku rzeczy:

  1. Aby wszystkie opcje które zapisały się w pliku pojawiły się w oknie z ustawieniami za każdym razem gdy wywołam to okno
  2. Aby program automatycznie łączył się z bazą danych podczas uruchamiania programu - więc funkcja z klasy SettingsFile na 100% musiałaby się uruchamiać w klasie MainWindow

Czy dobrze myślę ? A najlepiej wypadałoby zadać pytanie czy rozumnie wam to przedstawiłem ? Bo właśnie w ten sposób od początku myślę.

Jeżeli jakaś moja koncepcja jest zła, to podajcie jakieś pomysły.

1
zkubinski napisał(a):

wszystko zaczyna się w klasie AllSettings - ta klasa zajmuje się utworzeniem całej struktury JSON i wygląda jak poniżej - dla uproszczenia nie podaję szczegółów implementacyjnych

Klasa AllSettings to jest atrapa. Nie ma tu żadnego interfejsu, żadnej kontroli nad danymi. Jaki sens ma funkcja UpdateSettings() skoro wystawiasz publicznie implementację (showSettings)? Każdy może sobie dodać lub usunąć co tylko mu się podoba.

następnie wszystko z powyższej klasy wędruje do klasy SettingsFile - klasa ta zajmuje się zapisem do pliku całego utworzonego powyżej json-a

Ta klasa jest całkowicie zbędna. Skoro i tak trzymasz ustawienia w JSON, to zapis do pliku ogranicza się do max.10 linijek kodu.
Co w tej klasie robi QMessageBox czy parametry połaczenia z bazą danych to kompletnie nie mam pojęcia.

potem tworzę okno podrzędne w stosunku do MainWindow za pomocą klasy WindowSettings - to jest to okno, które widać w pierwszym poście i zadaniem tego okna jest zebranie wszystkich danych od usera i przekazanie ich do wszystkich obu klas tj. AllSettings & SettingsFile - klasa okna podrzędnego wygląda jak niżej

Skoro klasa SettingsFile zajmuje się zapisem do pliku to po jej komunikacja z WindowSettings?
Skoro WindowSettings komunikuje się bezpośrednio z SettingsFile to jaki jest sens istnienia AllSettings?
Po co trzymasz wskaźniki do QPalette czy QVBoxLayout itp. jako składowe klasy?

i ogólnie ja chcę zrobić to w ten sposób, że jak wywołuje się konstruktor klasy WindowSettings w klasie MainWindow - to przecież zanim on zbuduje całe to okno, to wcześniej odpali mi konstruktory klas AllSettings i SettingsFile czyli dane z pliku zostaną wczytane, zanim zbuduje mi się okno klasy WindowSettings bo na tą okoliczność napisałem funkcję w klasie SettingsFile która zwraca wskaźnik do całego wczytanego pliku json gdzieś w pamięci i ten fakt właśnie chcę wykorzystać do kilku rzeczy:

  1. Aby wszystkie opcje które zapisały się w pliku pojawiły się w oknie z ustawieniami za każdym razem gdy wywołam to okno
  2. Aby program automatycznie łączył się z bazą danych podczas uruchamiania programu - więc funkcja z klasy SettingsFile na 100% musiałaby się uruchamiać w klasie MainWindow

Czy dobrze myślę ? A najlepiej wypadałoby zadać pytanie czy rozumnie wam to przedstawiłem ? Bo właśnie w ten sposób od początku myślę.

Jeżeli jakaś moja koncepcja jest zła, to podajcie jakieś pomysły.

Zastosuj się do rady @Althorion.

Tak jak pisałem, jesteś w dobrym momencie, żeby siąść sobie z kartką papieru i ołówkiem, i wymyślić, jak chcesz mieć zorganizowany swój program. Jaki jest przepływ danych, co za co odpowiada, jak reagować w sytuacjach nadzwyczajnych, itd. Popisz sobie, co za co odpowiada; zdecyduj, czy nie próbujesz zrobić za wiele rzeczy jednym typem; popatrz jak to wszystko od siebie nawzajem zależy…

bo to co wrzuciłeś to jest pomieszanie z poplątaniem, bez ładu i składu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1