Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [C++/Java]Przechowywanie parametrów aplikacji graficznej
Forum PHP.pl > Inne > Hydepark
MateuszS
Witam, mamy taką często sytuację że w programie graficznym mamy coś takiego jak "Opcje", "Parametry" czy coś w tym stylu. Wpisujemy je np. w różnych inputach, textareach, radio buttonach a potem zapisujemy ustawienia. Dostęp do tych ustawień musi być z całego programu, z każdej klasy, która ich wymaga. Często jest tak że te klasy są gdzieś bardzo głęboko w hierarchii i nieodpowiednim jest pisanie czegoś takiego

Kod
//taki glupi przyklad
options = UI->getOptions();
UI->dom->parter->pokoj->krzeslo->setOptions(options)


Wiem że takie rozw. jak powyżej jest nieeleganckie i łamie zasady programowania obiektowego. Drugim rozwiązaniem może być przechowywanie tych ustawień w klasie statycznej i prosty dostęp za pomocą Opcje::parametr1. Jednak to mimo że wygodne, ponoć też jest niepoprawne (zalatuje zmiennymi globalnymi rodem z C).

Jaki jest wg Was najlepszy sposób na przechowywanie i przekazywanie tych danych? Lub ogólniej, jeżeli klasa "krzesło" (z powyższego przykładu) potrzebuje jakiejś danej z klasy "UI", to jak mu ją przekazać w najbardziej elegancki sposób?

Pozdrawiam
lukasz1985
W zmiennych globalnych nie ma nic złego. Naczytałeś się bzdur pseudo-profesjonalistów lub pracowników Google.
W programowaniu w takich językach jak Java / C# nie ma innych metod do tworzenia modułów jak użycie zmiennych statycznych / singletonów. Jedyną alternatywą jest odwrócenie zależności (Dependency Inversion/DI) co jest rozwiązaniem chorym umysłowo jeśli się temu bliżej przyjżeć.

To co ja bym zrobił to:
W C++ stworzył zmienne globalne "ustawienia" i "ui" i używał ich tam gdzie potrzeba.
W Javie stworzył klasy Ustawienia i UI i dał im pola "public static Ustawienia ustawienia" i "public static UI ui" przechowujące pojedyncze instancje tych klas, i w celu dostępu do nich używałbym importów statycznych .

Wewnątrz obiektów, które są elementami kompozycji danego modułu odwoływał bym się bezpośrednio do zmiennej globalnej / statycznej.

Nie ma czegoś takiego jak nieeleganckie rozwiązanie. Wszystko zależy od skali aplikacji, która musi być przewidziana na samym początku. Przede wszystkim przy aplikacjach stacjonarnych DI się nie sprawdza, chociaż może to być idealne rozwiązanie dla aplickaji internetowych.

Jeśli chcesz się jednak męczyć z DI/fabrykami, głupimi frameworkami DI typu Guice to droga wolna.

Jeszcze raz - zmienne globalne nie są złe, kiedy się wie, jak ich używać. Przede wszystkim w przestrzeni globalnej mogą leżeć typy wysoko abstrakcyjne, takie jak UI / ustawienia / menedżer zasobów czy obecny plik na którym pracujemy (jeśli zakładać że aplikacja pracuje na jednym pliku w czasie jej wykonywania), natomiast nie prymitywne typy int, float czy string.


MateuszS
W takim razie chyba najlepszym rozwiązaniem w C++ będzie stworzenie klasy statycznej z polami odpowiadającymi za poszczególne ustawienia oraz metodami set/get. Mój sceptycyzm wynika z tego raczej że do takich ustawień można się dostać z każdego miejsca programu. Ale to chyba najlepsze wyjście. Bo przekazywanie takie jak wcześniej zaprezentowałem, wężyk, to chyba jednak faktycznie mało przejrzyste.
lukasz1985
Ale czemu klasy statycznej? Wystarczy że stworzysz jedną instancję normalnej klasy w przestrzeni globalnej programu i wtedy będziesz miał dostęp do niej z każdego miejsca.

Co do wężyków. Jeśli w Twoim przypadku jest "krzesło" z metodą "setOptions" to nie musisz robić takich węzyków, jeśli używasz metody "ciągnięcia" danych zamiast ich "pchania". Chodzi o to, że dane potrzebne krzesłu mógłbyś w momencie tworzenia tego krzesła pobierać właśnie z obiektu globalnego.

Poza tym, takie węzyki trochę łamią zasadę enkapsulacji. Chodzi o to, że "włamujesz" się po krzesło przez dom->parter->pokoj. To może być przydatne czasami ale dobrze jest przemyśleć sobie czy takie rozwiązanie jest naprawdę bezpieczne.

Dam może inny przyklad, trochę bardziej realny.
Mam silnik 3d i występują tutaj obiekty przestrzenne (na przykład kula, stożek, sześcian itp). Każdy z nich ma swoją pozycję w przestrzeni. Powiedzmy, że jest to klasa 'Obiekt3d' , a z niej dziedziczą klasy "Kula", "Stożek" , "Sześcian". Natomiast każda z tych klas przetrzymuje referencję do obiektu klasy "Wektor3", która jest ową pozycją, składającej się z 3 pól "x" , "y" , "z" , które są zmiennymi "float".

Najłatwiej byłoby zrobić
"kształt->pozycja->x = float"

żeby zmienić pozycję obiektu na osi x w przestrzeni 3d. Ale co jeśli ze zmianą pzycji musi się wiązać jakaś inna operacja? Na przykład chcemy żeby przy zmianie pozycji sprawdzone zostało czy ten obiekt nie koliduje z innym obiektem. Jeśli zmienimy jedynie wartość x wewnętrznej referencji do pozycji obiektu wtedy coś takiego się nie stanie. Rozwiązaniem jest wprowadzenie do klasy "Obiekt3d" nowej metody "ustawPozycje(Wektor3 pozycja)"
i w niej można wykonywać wymagane operacje - zmienić pozycję używając zmiennej przekazanej do metody i skalkulować kolizje.

" Mój sceptycyzm wynika z tego raczej że do takich ustawień można się dostać z każdego miejsca programu."

No tak , można sie dostać ale co z tego? smile.gif Tutaj trzeba rozgraniczyć pomiędzy modułami i przyjąć określoną strategię komunikacji między nimi. Czyli na przykład mając moduł UI i moduł Ustawienia z na przykład jakąś domyślną wielkością czcionki, którą można zmienić - komunikujemy tą zmianę modułowi Ustawienia z modułu UI. Trudno bowiem na przykład zmieniać domyślną wielkość czcionki z modułu Drukowanie.


Generalnie tyle mogę powiedzieć z własnego doświadczenia.. mam jesszcze trochę innych, negatywnych takich jak programowanie oparte na zdarzeniach, ale nie polecam - 50% kodu to była obsługa zdarzeń, więc kod nic nie wnoszący do programu.

Aha i apropos tego UI. Jest jeszcze jedno rozwiązanie, jesli na prawdę nie chcesz zmiennych globalnych lub po prostu chcesz mieć wiele instancji jednej klasy: poszukaj w internecie na temat wzorca projektowego "Composite". Chodzi o to, ze kazdy element UI przetrzymuje referencje do rodzica, czyli "krzesła zna pokój"" w ktorym się znajduje, "pokój" zna "dom" a "dom" zna "UI" do którego należy więc można w momence dodania obiektu do UI przechwycić to UI z poziomu obiektu dodawanego.

Ja tego rozwiązana nie polecam. Jako, że używam raczej Javy - ciągle miałem problemy z pustymi wskaźnikami . Poza tym w rezultacie okazuje się się, że w danym momencie istnieje tylko jeden interfejs użytkownika. A żeby zmienić ten interfejs, wystarczy do zmienne globalnej, trzymającej ten interfejs (UI) przypisać po prostu inny... I nagle mamy zupełnie inne GUI - przydatne przy przechodzeniu pomiędzy ekranami. No i w rezultacie nie wiem czy wszystkie przypadki nie redukują się do pojedynczej instancji.

Generalnie kompozycja to trudny temat. Ja radzę nie "włamywać" się w obiekty złożone, co bardziej przypomina trochę "włażenie pod skórę" - hackowanie. Najważniejsza jest jednak kontrola nad tym.
W wielu przypadkach obiekty można uznać bardziej za struktury danych. W C++ jest do tego "struct" . Takie Ustawienia w wielu przypadkach mogłyby być po prostu właśnie zwykłą strukturą danych, co odpowiada klasie z upublicznionymi polami.
Jednak rzadko z czymś takim mam do czynienia. Warto sobie to przemyśleć zanim napisze się linijkę kodu smile.gif





MateuszS
Dzięki za odpowiedź. Mam kilka wątpliwości

Cytat
Chodzi o to, ze kazdy element UI przetrzymuje referencje do rodzica, czyli "krzesła zna pokój"" w ktorym się znajduje, "pokój" zna "dom" a "dom"


Ale czy przypadkiem wtedy nie mamy wężyka w drugą stronę?

Cytat
Ale co jeśli ze zmianą pzycji musi się wiązać jakaś inna operacja? Na przykład chcemy żeby przy zmianie pozycji sprawdzone zostało czy ten obiekt nie koliduje z innym obiektem. Jeśli zmienimy jedynie wartość x wewnętrznej referencji do pozycji obiektu wtedy coś takiego się nie stanie. Rozwiązaniem jest wprowadzenie do klasy "Obiekt3d" nowej metody "ustawPozycje(Wektor3 pozycja)"

No tak, pisałem o tym, ale tu mamy dziedziczenie, jest trochę inna sytuacja. Ale nawet gdy nie będzie dziedziczenia to wężyk pozostanie tyle że zakończy się metodą a nie bezpośrednim dostępem do pola.

Cytat
Aha i apropos tego UI. Jest jeszcze jedno rozwiązanie, jesli na prawdę nie chcesz zmiennych globalnych lub po prostu chcesz mieć wiele instancji jednej klasy: poszukaj w internecie na temat wzorca projektowego "Composite".

Piszę na razie w Qt, tam w sumie jest podobny mechanizm, przekazywania rodzica ale to raczej nie rozwiązuje mojego dylematu bo wężyk zostanie.

Może przybliżę bardziej mój "problem". Mianowicie mam główną klasę odpowiedzialną za okno. Klasę Ramka, która jest pewnym obszarem tego okna oraz klasę Punkt, która jest obszarem w Ramce (może być kilka punktów w ramce). Oto pseudokod, który to reprezentuje:

http://wklej.org/id/1186476/

Jak widać, skoro wszystkie buttony są w klasie GUI, pobierając wartości z tych "inputów", "buttonów", trzeba je tak zapisać aby dostęp do tych ustawień był z całej aplikacji. Metody, które znam to tylko wężyk ramka->punkt->setUstawienie(ustawienie) lub klasa statyczna (oraz zaproponowana przez Ciebie klasa z dostępem globalnym). Ale może moja koncepcja jest zła? Wydaje mi się że słusznie nie zastosowałem tutaj dziedziczenia, bo jak napisał pan Grębosz w swojej książce, dziedziczenia używamy gdy jedna klasa jest jakimś podtypem innej a nie gdy jedna zawiera się w drugiej.
lukasz1985
Cytat
Cytat
Chodzi o to, ze kazdy element UI przetrzymuje referencje do rodzica, czyli "krzesła zna pokój"" w ktorym się znajduje, "pokój" zna "dom" a "dom"


Ale czy przypadkiem wtedy nie mamy wężyka w drugą stronę?


Właśnie nie. Zapomniałem powiedzieć o kilku sprawach.
Wyobraź sobie taki układ klas (pisane łamaną Javą):

  1. class Kompozyt{
  2. Kompozyt rodzic;
  3. UI ui; // Ui do którego należy ten kompozyt
  4. Kompozyt pobierzRodzica() {
  5. return rodzic;
  6. }
  7.  
  8. Lista<Kompozyt> dzieci = new Lista<Kompozyt>(); //Lista kompozytów - dzieci
  9.  
  10. protected void dodajDziecko(Kompozyt noweDziecko) {
  11. dzieci.dodaj(noweDziecko);
  12. noweDziecko.ustawRodzica(this);
  13. }
  14.  
  15. void ustawRodzica(Kompozyt nowyRodzic){
  16. rodzic = nowyRodzic;
  17. }
  18.  
  19. void ustawUI(UI noweUI) {
  20. ui = noweUI;
  21. }
  22.  
  23. UI pobierzUI() {
  24. Kompozyt biezacyRodzic = this;
  25. do{
  26. if (biezacyRodzic instanceof UI) {
  27. return (UI) biezacyRodzic; // rzutowanie typów
  28. } else {
  29. biezacyRodzic = biezacyRodzic.pobierzRodzica();
  30. }
  31.  
  32. } while(biezacyRodzic != null); // przerwanie pętli jeśli rodzic jest null.
  33. }
  34.  
  35.  
  36. }
  37.  
  38. class UI extends Kompozyt {
  39. Ustawienia ustawienia;
  40. Ustawienia pobierzUstawienia(){
  41. return ustawienia;
  42. };
  43.  
  44. @Override // metoda nadpisana
  45. void dodajDziecko(Kompozyt dziecko) {
  46. super.dodajDziecko(dziecko); // wywołanie tej samej metody klasy kompozyt
  47. dziecko.ustawUI(this);
  48. }
  49. };
  50.  
  51. class Krzesło extends Kompozyt{};
  52.  
  53. class Dom extends Kompozyt{};
  54.  
  55.  
  56. // A teraz kod który operuje na tych klasach
  57. public static void main(String[] args) {
  58. UI ui = new UI();
  59. Dom dom = new Dom();
  60. Krzesło krzesło = new Krzesło();
  61. ui.dodajDziecko(dom);
  62. dom.dodajDziecko(krzeslo);
  63.  
  64. boolean test = (ui == dom.pobierzUI()); // Zwróci true;
  65.  
  66. }
  67.  



Chodzi o to, że teraz każdy obiekt może wywołać UI, kiedy tego będzie potrzebował, jedynym warunkiem jest to, że gdzieś w tej hierarchii obiektów w kompozycji musi być obiekt typu ui, bo na przykład gdyby użyć tego kodu:
  1. public static void main(String[] args) {
  2.  
  3. Dom dom = new Dom();
  4. Krzesło krzesło = new Krzesło();
  5.  
  6. dom.dodajDziecko(krzeslo);
  7.  
  8. boolean test = (null == dom.pobierzUI()); // Zwróci true;
  9.  
  10. }
  11.  


Więc tutaj nie ma wężyków i nie ma modułu w globalnej przestrzeni.



Co do twojej koncepcji i Qt - nie znam Qt ale pytanie brzmi: kiedy będziesz potrzebował dostępu to ustawień?
Chodzi o to, ze mógłbyś nadpisać metody, a pewnie takie są, które są wykonywane w momencie dodawania elementów do okna lub rodzica - kontenera lub podczas wyświetlania. I w nich wykonywać kod odpowiedzialny za odczytywanie ustawień. W twoim przypadku punkt musiałby dziedziczyć po czymś takim ja QWidget i nadpisać jakąś metodę, która jest wywoływana w momencie dodania/wyświetlenia tego punktu - jaka to metoda - nie wiem - to znajdziesz pewnie w dokumentacji.








I jeszcze jedna prosta rada: jeśli masz n punktów w ramce, która jest w okienku to jeśli potrzebujesz iterować te punkty.. nie rób tego z poziomu okna.
Czyli nie rób czegoś takiego:

W klasie "Okno" zawierającej pole "ramka"
for(int i = 0 ..... i++) {
punkt = ramka->punkt[i];
... operacje na punkcie ...
}



postaraj się takie iteracje i tym samym wszystkie operacje zawrzeć w klasie która zawiera punkty bezpośrednio:



W klasie Ramka:
void metoda() {

for(int i = 0 ..... i++) {
punkt ->punkt[i];
... operacje na punkcie ...
}
}

W klasie Okno:
ramka->metoda();



To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.