czy ten kod nie łamie hermetyzacji klas?

czy ten kod nie łamie hermetyzacji klas?
ZK
  • Rejestracja: dni
  • Ostatnio: dni
0

jak w tytule, bo w mediatorze chyba zapędziłem się w kozi róg - teoretycznie on ułatwia komunikację ale jak już chce się "dopieszczać" wizualnie kontrolki, to już tak słabo to widzę. W sumie mam dwa problemy - obecny i przyszły (który w sumie powstaje, bo się kapnąłem przy pisaniu rozwiązania)

  1. Obecny problem z hermetyzacją polega na tym, że w klasie MainWindow pobieram adres tego okna podczas rejestracji do mediatora i wygląda to tak
Kopiuj
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), mainWidget{new QWidget(this)}
{
    this->setObjectName(QString("MainWindow"));

    //dla uproszczenia taki kod
    myWindowSettings = new WindowSettings(this);

    dataTransferMediator = new Mediator(this); //powstaje okno klasy WindowSettings
    dataTransferMediator->registerWindow(myWindowSettings); //pobieram wskaźnik tego okna do mediatora
}

no i tutaj przy pisaniu kodu w mediatorze - (o czym za chwilę) - zastanawiam się czy nieświadomie nie "obszedłem" zasady hermetyzacji klas?

Problem 1 (obecny) - w funkcji registerWindow pobieram wskaźnik okna klasy WindowSettings - niby nic niewinnego ale jednak...

Kopiuj
void Mediator::registerWindow(QWidget *ptrWidget)
{
    bool isRegister = false;

    if(ptrWidget){ //prawdopodobnie pierwsze obejście hermetyzacji klas
        ptrChildWindowSettings = ptrWidget; //pobieram wskaźnik CAŁEGO OKNA WindowSettings
        isRegister = true;
        emit registerNewWindow(isRegister);
    }
}

w klasie WindowSettings składowe prywatne wyglądają mniej więcej tak - dla uproszczenia usunąłem kod o którym nie jest mowa - daję w celu głębszego wglądu w sytuację

Kopiuj
class WindowSettings : public QDialog
{
    Q_OBJECT

private:
    //usunięto kod w celach przejrzystości

    Page1 *page1;
    Page2 *page2;
};
Kopiuj
WindowSettings::WindowSettings(QWidget *parent): QDialog{parent}, mainVLayout(new QVBoxLayout(this))
{
    this->setObjectName(QString("WindowSettings"));

    page1 = new Page1(this); //tworzę Page1 - a za chwilę będę miał do niego dostęp...
    page2 = new Page2(this); //tworzę Page2 - a za chwilę będę miał do niego dostęp...

    stackedWidget = new QStackedWidget(this);
    
    stackedWidget->addWidget(page1);                //![Strona 1]
    stackedWidget->addWidget(page2);                //![Strona 2]

    hLayoutStacked->addWidget(treeStackedWidget);
    hLayoutStacked->addWidget(stackedWidget);
}

w funkcji isRegisteredWindow po pobraniu adresu okna klasy WindowSettings zaczynam się dobierać do składowych PRYWATNYCH klasy Page1 i Page2 - jak poniżej

Kopiuj
Error::RegisterWindowErrors Mediator::isRegisteredWindow(bool isRegister)
{
    if(!isRegister){ //niby nic takiego, ALE NIŻEJ...
        return Error::RegisterWindowErrors::NotRegistered;
    }

    window = qobject_cast<WindowSettings*>(ptrChildWindowSettings); //tutaj już chyba zaczynam łamać hermetyzację klas
    //bo jawnie pobrałem wskaźnik klasy WindowSettings i NIŻEJ DOBIERAM SIĘ DO SKŁADOWYCH PRYWATNYCH TEJ KLASY!!!

    if(!window){
        return Error::RegisterWindowErrors::NullPointer;
    }

    page1 = window->findChild<Page1*>(); //tutaj dobieram się do składowych prywatnych klasy WindowSettings
    if(!page1){
        return Error::RegisterWindowErrors::MissingPage1;
    }

    //po dobraniu się do składowych prywatnych klasy WindowSettings, dobrałem się do funkcji publicznych klasy Page1
    QObject::connect(page1, &Page1::ipAddress, this, &Mediator::receivedIPAddress); //o tutaj się dobieram

    QObject::connect(this, &Mediator::validInputIPaddress, page1, &Page1::setInputFieldValidationColor);
    QObject::connect(this, &Mediator::invalidInputIPaddress, page1, &Page1::setInputFieldInvalidColor);

    QObject::connect(page1, &Page1::port, this, &Mediator::receivedPort);
    // QObject::connect(this, &Mediator::validInputIPaddress, ) //tutaj się kapnąłem, że... czeba dobrać się do składowych prywatnych Page1

    page2 = window->findChild<Page2*>(); //tutaj się dobieram do prywatnej składowej klasy WindowSettings
    if(!page2){
        return Error::RegisterWindowErrors::MissingPage2;
    }

    return Error::RegisterWindowErrors::RegisterSuccess;
}

no więc co o tym sądzicie? złamałem hermetyzację klas? - powyższe dotyczy obecnego kodu, który w sumie już mam ale rozbudowuję aplikację i chcę dodać "fajerwerki" informujące w sposób wizualny, że ludzik zrobił coś źle i o tym dalej poniżej:

Problem 2 - który jeszcze nie powstał ale ukazał skalę problemu? Prawdopodobne drugie obejście hermetyzacji klas

postanowiłem dodać kolory dla pola QLineEdit aby informowało wizualnie użytkownika, że to co wpisał jest dobrze lub źle, więc dla testu ustawiłem kolory dla jednego pola, które przyjmuje adres IP - dla tego pola nie złamałem hermetyzacji ale przy większej ilości pól zaczyna się problem i zastanawiam się czy zrobię dobrze?

Bo nie chcę dla każdego pola pisać funkcji ustaw "odpowiedni kolor"

dla zobrazowania pokażę jak to wygląda dla jednego pola

przed wpisaniem lub po skasowaniu czegokolwiek - pole wygląda tak
screenshot-20241122121659.png
podczas wprowadzania - nie znamy jeszcze wyniku validacji, pole wygląda tak
screenshot-20241122121753.png
po wprowadzeniu poprawnego wpisu, który natychmiast zostaje zwavlidowany poprawnie, pole wprowadzania wygląda tak
screenshot-20241122121849.png

no a skoro nie chcę powielać dużej ilości funkcji dla tego samego zadania, to wpadłem na pomysł, żeby zrobić jedną funkcję do tej pracy

czyli Mediator ma funkcję void Mediator::errorValidate(Error::ValidationErrors errorCode, FieldType fieldType) która sprawdza, jaki rodzaj błędu został wygenerowany i przez jakie pole

ale prawdziwa zabawa zaczyna się tutaj - bo mediator w końcu nie ma pojęcia, które konkretne pole poinformować o tym, że coś jest źle lub dobrze wpisane przez ludzika i znowu wpadłem na pomysł aby do funkcji, które przesyłają wpisany tekst do mediatora, przesyłały również wskaźnik tego pola które ten tekst wysyła - kod niżej

Kopiuj
void Page1::setIpAddress(const QString &address)
{
    emit this->ipAddress(address); //tutaj jeszcze nie łamię hermetyzacji, bo jeszcze tego nie napisałem
}

ale czy przypadkiem nie złamię hermetyzacji, jeżeli zrobię coś takiego?

Kopiuj
Page1::Page1(QWidget *parent): QWidget{parent}, mainVlayout(new QVBoxLayout(this))
{
    this->setObjectName(QString("Page1"));    

    //dla uproszczenia usunąłem wszystko i zostawiłem, to na czym chcę się skupić

    lineEditServerAddress = new QLineEdit(this); //pole, które pobiera adres IP od użytkownika
}
Kopiuj
void Page1::setIpAddress(const QString &address)
{
    emit this->ipAddress(address, lineEditServerAddress); //czy tutaj już łamię zasady hermetyzacji?
    //bo w sumie wysyłam do mediatora adres prywatnej składowej klasy i potem robię sobie tam co chcę...
}
RodionGork
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 47
0

Jak decydujesz co powinno być "priwatne"?

To jest abstrakcja... "private" sa wnętrzności jakie może zmienia się w przyszłości a nie chcemy że taka zmiana złamala zewnętrzny kod

W twoim przypadku nie masz od kiego chowac kod, więc "prywatność" jest troche nejasna rzecz.

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1029
0
window = qobject_cast<WindowSettings*>(ptrChildWindowSettings); //tutaj już chyba zaczynam łamać hermetyzację klas

Tak

Ciężko mi powiedzieć cokolwiek o tym kodzie, bo zgaduje, że jego kształt zależy mocno od tego jak działa QT a na tym się nie znam.

Na pewno manewry takie jak newKonstrutktor(this) są zazwyczaj złe, bo robią ci się cykle. Częstym rozwiązaniem cykli jest po prostu wrzuczenie wszystkiego do jednej kupy a następnie pomyślenie jak to zrobić lepiej.

To co widzę tutaj to wszystko gada ze wszystkim i te osobne obiekty i komunikacja pomiędzy nimi to tylko sztuka dla sztuki. Wrzuć jak możesz jak to by wyglądało w jednej klasie (plus osobne klasy, jeśli komponenty QT tego wymagają)

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
slsy napisał(a):
window = qobject_cast<WindowSettings*>(ptrChildWindowSettings); //tutaj już chyba zaczynam łamać hermetyzację klas

Tak

ok, po dłuższej analizie doszedłem do wniosku, że w sprytny sposób łamię tą hermetyczność i chyba znalazłem winowajcę - operatory rzutowania - bo domyślam się, że one sprawdzają czy to co rzutujemy z czegoś - na coś jest z klasy pochodnej - bo rzutowanie może być w dół hierarchii i w górę hierarchii ale te operatory już najprawdopodobniej nie sprawdzają modyfikatorów dostępów w klasie, ze szczególnym naciskiem na private i dlatego mogłem zrobić, to co zrobiłem. Czyżby niechcący znalazłem niedopracowanie operatorów rzutowania?

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1029
0
zkubinski napisał(a):

Czyżby niechcący znalazłem niedopracowanie operatorów rzutowania?

Nie, po prostu findChild jest publiczne. Nie wiem co to dokładnie robi, ale pewnie dla przejrzystości usunąłeś kluczowy kawałek kodu

Rzutowanie nie ma nic do rzeczy. Jak qobject_cast<WindowSettings*> zwraca ci WindowsSettings* to język cię broni przed użyciem prywatnych pól. findChild<Page1*> to nie jest page1. Normą jest taki kod (np. w takiej Javie):

Kopiuj
class Foo {
  private int x;

  public void setX(int x) {this.x = x}
}

Co z tego, że x jest prywatne skoro możesz się dobrać przez publiczną metodą setX?

Dodam jeszcze, że rzutowanie w dół jest często błędem w designie i w tym przypadku pewnie tak jest. Po to masz hermetyzację i dziedziczenie, żeby ukrywać możliwości twojego kodu pod interfejsem. Rzutowanie to po prostu ominięcie tych mechanizmów

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
slsy napisał(a):
zkubinski napisał(a):

Czyżby niechcący znalazłem niedopracowanie operatorów rzutowania?

Nie, po prostu findChild jest publiczne. Nie wiem co to dokładnie robi, ale pewnie dla przejrzystości usunąłeś kluczowy kawałek kodu

nie chcę się upierać ale tutaj postanowiłem użyć tego co jest dostępne w klasie QObject - bo wszystkie klasy po tym dziedziczą

wcześniej miałem taki kawałek kodu i działało tak samo... i nie używałem tam findChild

Kopiuj
void Agent::registerWindow(QWidget *ptrWidget)
{
    if(ptrWidget && !registerWidgets.contains(ptrWidget)){
        registerWidgets.append(ptrWidget);
    }

    foreach (QWidget *ptr, registerWidgets){
        window = qobject_cast<WindowSettings*>(ptr);
        if(window){
            QObject::connect(window, &WindowSettings::showPageNumber, this, [&](QWidget *ptr){
                page1 = qobject_cast<Page1*>(ptr);

                if(page1){
                    qInfo()<<"agent ma adres page 1" << page1;
                    QObject::connect(page1, &Page1::ipAddress, this, &Agent::receivedIPAddress);
                    QObject::connect(page1, &Page1::port, this, &Agent::receivedPort);
                    QObject::connect(page1, &Page1::databaseName, this, &Agent::receivedDatabaseName);
                    QObject::connect(page1, &Page1::login, this, &Agent::receivedLogin);
                    QObject::connect(page1, &Page1::password, this, &Agent::receivedPassword);

                    QObject::connect(this, &Agent::validInputIPaddress, page1, &Page1::setValidIPcolor);
                } else {
                    qInfo()<< "nie mozna zainicjalizowac page1";
                }
            });
        }else{
            qInfo()<< "obiekt nie jest windowsettings";
        }
    }
}
SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1029
0

To, że coś jest niekoniecznie oznacza, że powinieneś tego używać.

wcześniej miałem taki kawałek kodu i działało tak samo... i nie używałem tam findChild

foreach (QWidget *ptr, registerWidgets){ iteruje po wszystkich komponentach. Ta sama zasada działania

Zobacz sobie jak piszą w dokumentacji

Kopiuj
class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget();

signals:
    void buttonClicked();

private:
    QPushButton *myButton;
};

MyWidget::MyWidget()
{
    myButton = new QPushButton(this);
    connect(myButton, SIGNAL(clicked()),
            this, SIGNAL(buttonClicked()));
}

Obiekt sam się rejestruje co brzmi sensownie. Jak obiekt robi to sam to możesz zrobić jego kilka wersji i klasa, która trzyma te komponenty może polegać na abstrakcji.

ZK
  • Rejestracja: dni
  • Ostatnio: dni
0
slsy napisał(a):

Obiekt sam się rejestruje co brzmi sensownie. Jak obiekt robi to sam to możesz zrobić jego kilka wersji i klasa, która trzyma te komponenty może polegać na abstrakcji.

ok, rozumiem tylko nie wiem jak obiekt może sam się rejestrować? Dorzuca swój wskaźnik do jakiejś tablicy w QObject i dlatego mogę użyć findChild?

po przeczytaniu kilku rzeczy już doszedłem, że zbyt mocno wykorzystałem możliwości findChild bo dostałem się do członków prywatnych. Więc wniosek jest taki, że to ja tak naprawdę powinienem dać metody publiczne - znaczy zrobić interfejs tak, aby wystawić funkcje, które coś zmieniają - nie chodzi tutaj, że w tej materii mam brak wiedzy ale po prostu w tym miejscu źle przemyślałem pisanie aplikacji, co dało takie konsekwencje.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10230
0

Moim zdaniem nie łamie hermetyzacji, dlatego że i tak te dwie klasy dotyczą widoku i QT. Tak czy tak muszą się wołać nawzajem, i tak czy tak zmiana w jednym wymusza zmianę w drugim. Także zostaw tak jak jest.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.