czy ten kod nie łamie hermetyzacji klas?

czy ten kod nie łamie hermetyzacji klas?
ZK
  • Rejestracja:około 19 lat
  • Ostatnio:2 miesiące
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ę...
}

bla
edytowany 1x, ostatnio: zkubinski
RodionGork
  • Rejestracja:4 miesiące
  • Ostatnio:3 miesiące
  • 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:około 7 lat
  • Ostatnio:około 15 godzin
  • Postów:862
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:około 19 lat
  • Ostatnio:2 miesiące
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?


bla
SL
  • Rejestracja:około 7 lat
  • Ostatnio:około 15 godzin
  • Postów:862
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:około 19 lat
  • Ostatnio:2 miesiące
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";
        }
    }
}

bla
SL
  • Rejestracja:około 7 lat
  • Ostatnio:około 15 godzin
  • Postów:862
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:około 19 lat
  • Ostatnio:2 miesiące
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.


bla
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:około 14 godzin
  • Lokalizacja:Laska, z Polski
  • Postów:10053
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.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)