Wskazówki dotyczące projektowania/programowania obiektowego.

1

Witam
Jestem początkującym programistą jeżeli chodzi o OOP (wcześniej pisałem strukturalnie).
Mam kilka pytań dotyczących projektowania/programowania obiektowego.
Pytania dotyczą aspektów nad którymi często się zastanawiam pisząc program
aby uzyskać dobrą czytelność jak najmniej powiązań itp. ponieważ uważam,
że OOP ułatwia budowanie aplikacji jeżeli się je dobrze zrozumie i umie wykorzystać.

1)
Czy w tego typu klasie:

class Punkt {

     public:
     Punkt(int x, int y);

     private:
     int x,y;
}

może być metoda: bool porownaj(const Punkt& p);
czy to narusza SRP (single responsibility principle) i trzeba stworzyć
do tego nową klasę np Porównywanie? Jak rozpoznać czy klasa ma jedną lub wiele odpowiedzialności?
Z pewnej książki wiem że długość kodu w klasie może być wyznacznikiem ale
mogą być klasy krótkie z wieloma odpowiedzialnościami.

2)
Czy klasa powinna zawierać metodę wyświetl?
(oczywiście taka klasa, której obiekt jest przeznaczony do pokazania go na ekranie)
W w wielu przykładach klas w internecie widziałem, że np klasa Figura abstrakcyjna ma metodę
wyświetl jednak czytając inne artykuły bardziej o projektowaniu to nie powinno tak być ponieważ
klasa powinna mieć tylko jeden powód do zmiany, a tu by były 2 powody czyli sposób prezentowania na
ekranie i przechowywanie/zmiana informacji o tym obiekcie. Jednak może takie rozumowanie
stosuję się bardziej przy większych aplikacjach z interfejsem użytkownika a w konsoli do
wyświetlenia dowolnego obiektu (np obiekt klasy Element, który przechowuje położenie int x,y; i char znak;)
przyjmuje się że taka metoda dla obiektu może być ?

3)
Jak rozumiem hermetyzacje:
Hermetyzacja to ukrywanie implementacji
a udostępnianie interfejsu. Klient klasy(może być inna klasa, która deleguje odpowiedzialność) nie
musi wiedzieć jak klasa działa, w jaki sposób realizuje usługę, korzysta tylko z interfejsu w celu
zrealizowania swoich działań. Klient klasy nie może odwoływać się do składowych prywatnych tej klasy
(np. poprzez wskaźnik) to by było wbrew zasad hermetyzacji.

Kiedy np mam klasę klasaA , która składa się z klasy klasaB a ta jeszcze się składa z klasaC
i potrzebuję informacji o czymś co jest przechowywane i udostępniane przez interfejs w klasie klasaC
to muszę dodać w klasach nadrzędnych(klasaB i klasaA) odpowiednie metody aby to zrobić?
Często jak potrzebuję takich informacji i myślę że tyle metod trzeba dodać aby to zrobić to wydaje
mi się, że coś jest źle zaprojektowane, albo obiekty są zbyt powiązane itp.

**4) **
Jak pisać programy tak aby uzyskać jak największą skuteczność
(przez skuteczność rozumiem brak duplikacji kodu, kod czysty, zrozumiały
jak najlepiej odwzorowujący rzeczywistość)
Załóżmy, że istnieją klasy klasaA i klasaB oraz ich
instancje obiektA i obiektB.

Jak powinno się korzystać z obiektów (np w metodzie głównej programu)

if( obiektA.sprawdz_stan() ) 
            obiektA.wykonaj_czynnosc();

czy lepiej od razu w ciele metody wykonaj_czynnosc()
sprawdzić stan metodą sprawdz_stan() i wykonać czynność?

oraz

if( obiektA.sprawdz_stan() )
          obiektB.wykonaj_czynnosc();

czy w obiekcie obiektB zawrzeć wskaźnik
na obiekt obiektA i w ciele metody
wykonaj_czynność obiektu obiektB
sprawdzić : if( obiektA->sprawdz_stan() ) obiektB.wykonaj_czynnosc();
Czy są jakieś zasady w związku z tym?

5)
Rozumiem pojęcie kompozycji po prostu obiekt nadrzędny aby
wykonać swoje zadanie zleca innym obiektom zadania podrzędne
przy czym obiekt część nie może istnieć bez całości.
Jak to jest z asocjacją (trochę słabsza zależność od kompozycji)?
Czy często się ją stosuje? Jak ją skutecznie stosować aby uniknąć
zbędnych zależności miedzy obiektami?

0

Chyba moj post jest zbyt pokrecony i niezrozumialy bo nikt nie odpowiada :P

1

czy to narusza SRP (single responsibility principle) i trzeba stworzyć
do tego nową klasę np Porównywanie?

To zależy jak spojrzeć. Można argumentować zarówno, że narusza jak i nie.

Owszem, teoretycznie definiowanie współrzędnych x,y a porównywanie obiektów to osobna odpowiedzialność. Z drugiej strony i to, i to można określić wspólnie "obliczeniami matematycznymi na punktach współrzędnych", a porównywanie to jedynie jeden z rodzajów tych obliczeń (zwykle robi się też funkcje do obliczania odległośći między punktami, do dodawania punktów etc). Więc od strony teoretycznej i tak, i tak można.

Róznica jest praktyczna. Trzymanie tego w jednej klasie jest prostsze i łatwiejsze w ogarnięciu. Więc jest taka korzyść.

Trzymanie kodu porównywania w osobnej klasie (czy w zasadzie może nawet funkcji trzymanej luzem gdzieś?) pozwala na lepszą elastyczność w przyszłości (teraz masz tylko Punkt, ale wyobraź sobie, że kiedyś będziesz miał np. klasę Point3D na określenie punktu w przestrzeni 3D, czy klasę PolarPoint w którym punkt byłby reprezentowany przez współrzędne biegunowe). I wyobraź sobie że chcesz porównać te wszystkie rzeczy ze sobą. Trzymanie wtedy całego kodu porównywania w klasie Punkt byłoby czymś strasznie nieeleganckim.

Róznica jednak jest taka, że na chwilę obecną, jeśli masz jedną klasę Punkt nie jest to nieeleganckie. Więc raczej nie ma nic złego w trzymaniu tego na chwilę obecną w jednej klasie, ale z myślą jednak, że kiedyś może będziesz potrzebował to zrefaktoryzować(przerobić od strony kodu) w ten sposób, że wydzielisz kod porównywania poza klasę Punkt.

Kod, który piszesz nie musi być czymś, co traktujesz na zasadzie "raz napisałem i będzie to już na wieki". Dużo rzeczy można potem zmienić przerabiając program.

Czy klasa powinna zawierać metodę wyświetl?
(oczywiście taka klasa, której obiekt jest przeznaczony do pokazania go na ekranie)

To zależy w jaki sposób postrzegasz separacje pomiędzy poszczególnymi zagadnieniami w kodzie (separation of concerns).
Jeśli uznasz, że opis obiektu (jego "model") powinien być oddzielony od kodu odpowiedzialnego za grafikę (czyli od "widoku"), to dązysz do tego, żeby trzymać dane obiektu osobno, a do wyświetlania stosujesz jakieś osobne klasy czy funkcje. Po to, żeby uniezależnić dane od sposobu ich prezentacji.

Jednak jeśli traktujesz widok i model jako część jednego zagadnienia, to wtedy trzymasz to razem.

Jednak z tego co widzę wokół to podejście, który oddziela model od widoku zwykle jest bardziej elastyczne i bardziej trwałe (odporne na przepisywanie rzeczy od nowa) niż łączenie tego razem.

1
  1. Nie popadajmy w paranoje ;) Niemniej jeśli algorytm porównywania jest jakiś bardzo skomplikowany to warto wyciągnąć go do osobnej klasy.
  2. W takiej sytuacji można (i generalnie należy) użyć delegacji. Tzn zrobić klasę FiguraUI która potrafi namalować figurę a przechowuje w sobie referencje do obiektu typu Figura z warstwy logiki aplikacji. W praktyce niektórzy to pomijają zwyczajnie ze względu na to żeby nie mieć miliona klas. Szczególnie jeśli wiesz na 100% że nie będziecie zmieniać warstwy widoku w tej aplikacji. Zdrowy rozsądek to podstawa, bo często nie ma sensu robić meta-turbo generycznego rozwiązania jeśli wiadomo że wystarczy dużo bardziej trywialna implementacja.
  3. Generalnie tak, ale często taka sytuacja oznacza że próbujesz gdzieś "wysoko" zrobić jeden wielki algorytm zamiast podzielic go na kawałki. Załóżmy że chcesz coś policzyć w klasie A na podstawie danych z klasy C (A zawiera B, B zawiera C). Można wykonać część obliczeń na poziomie C, część na poziomie B a do A lecą już częściowo przetworzone dane.
  4. Nie ma reguły i raczej jest to kwestia logiki aplikacji i ewentualnie tego gdzie i jak chcesz obsługiwać "alternatywne" ścieżki. Bo co jeśli warunek sprawdzasz w klasie B i nie jest spełniony? Jak poinformujesz kogoś "wyżej" że metoda sie nie wykonała?
  5. Stosuje się pewnie dużo częściej niż Kompozycje ;) Wszystkie Serwisy zwykle działają na takiej zasadzie. Masz jeden bezstanowy serwis z którego korzysta wiele obiektów. Nie do końca rozumiem czym są "zbędne zależności".
1
XiLk napisał(a):

Chyba moj post jest zbyt pokrecony i niezrozumialy bo nikt nie odpowiada :P

Nie, to raczej kwestia tego typu, że na wszystkie pytania można odpowiedzieć "to zależy". "It depends" to ulubiona i najczęściej udzielana odpowiedź na forach programistycznych. I co najlepsze - jest to odpowiedź jak najbardziej prawidłowa ;)

0

Dzięki za odpowiedzi.

Doszedłem do takich wniosków jeżeli chodzi o tę zasadę SRP:

-to czy klasa spełnia zasadę SRP może zależeć od tego jak interpretujemy
odpowiedzialność tej klasy tj napisał użytkownik @LukeJL może być klasa
do definiowania współrzędnych x,y i porównywanie to osobna ospowiedzialność
lub klasa do obliczeń matematycznych na punktach współrzędnych wtedy klasa będzie
spójna;

-klasa powinna realizować tylko te zadania, które składają się na odpowiedzialność tej klasy;

-klasa (a w zasadzie jej nazwa) jest o jeden poziom abstrakcji wyżej niż zaimplementowane
w niej metody publiczne/prywatne;

-każda metoda w klasie powinna zajmować się jedną rzeczą, mieć jeden powód do jej wywołania,
realizacją tej metody mogą być inne metody, które są o jeden poziom abstrakcji niżej niż ona sama;

-jeżeli w klasie kilka metod łączy pewien wspólny kontekst
(jakaś wspólna odpowiedzialność dla której można przypisać rzeczownik)
warto wydzielić je do osobnej klasy, która będzie klasą podrzędna
(o jeden poziom abstrakcji niżej) i delegować zadania.

Nie do końca rozumiem czym są "zbędne zależności"

@Shalom właśnie dokładnie chodzi mi oto o czym wspomniałeś w 4 punkcie
czyli jakieś powiązania klas wskaźnikami, które powodują, że ciężko jest
poinformować inne obiekty o jakimś stanie przez co trzeba dodawać sztucznie
metody do klas, które o czymś informują. Zbędne zależności to też jak graf powiązań
między klasami jest strasznie skomplikowany np jedna klasa ma wskaźniki do 3 innych lub więcej itp.

A jeżeli chodzi o punkt 3 to załóżmy, że piszę sobie klasy do gry 2D.
Mam klasę AntyTerrorysta, który może strzelać z dowolnej broni a więc ma
wskaźnik na klasę abstrakcyjną broń. Broń ma wskaźnik na klasę magazynek
aktualnie w niej załadowany, a magazynek składa się z listy pocisków, które
można wystrzelić z broni. Jak teraz obsługiwać obiekty, które są tak nisko położone
w hierarchii np aktualnie wystrzelony pocisk, żeby np zmieniać jego położenie?
Mam napisać w każdej klasie metode get_wystrzelony_pocisk() i potem dodawać
je do jakiejś listy wystrzelonych pocisków? Czy mam tworzyć przysłowiowe 1000 metod
w klasie najwyższej (tj. Antyterrorysta) aby pracować na klasach niższych w hierarchii?
Po prostu jeżeli obiekty są tak poukrywane
to nie wiem jak na nich pracować.

2

Musisz zostawić odpowiednie "działania" na odpowiednich poziomach abstrakcji. Inaczej burzysz zasadę pojedynczej odpowiedzialności. Przecież ten element programu, który miałby "pracować" na poukrywanych obiektach, o ktorych piszesz to jakiś kolos - multi-konroler, który wie wszystko o wszystkim. A przecież nie do tego dążymy.
W Twoim przykładzie można np. dodać metodę wystrzel() do klasy broń, która pobierze kolejny pocisk z aktualnie załadowanego magazynka (np. dajKolejnyPocisk() w klasie magazynek) i uruchomi jakiś kontroler lotu pocisku w danym środowisku (woda, atmosfera ziemska, próźnia, itp). W każdym razie niech terrorysta potrafi tylko wcisnąć cyngiel, broń niech potrafi pobrać pocisk i go wystrzelic, magazynek ma potrafić tylko przechować i podać kolejny pocisk i tyle - tym sposobem ukrywasz implementację między poszczególnymi komponentami programu.

Oczywiście to wszystko model - od Ciebie zależy, które elementy modelowanego świata potrzebujesz mieć w programie, a które są zupełnie zbędne.

0

Ok mniej wiecej rozumiem ideę.
Czyli np gracz strzela z broni (została wywołana metoda strzelaj() na terroryscie)
i już klasy podrzędne resztę nam załatwią i pocisk powinien być w powietrzu.
A jeżeli chcę sprawdzić czy ten wystrzelony pocisk w coś trafił i jeżeli tak
to zniszczyć coś to też realizuję to "w środku" np w tym kontrolerze lotu?
Wtedy klasa terrorysty nawet nie bedzie musiala ingerować w to zniszczenie
dobrze rozumiem?

1

A od kiedy terrorysta ingeruje w to czy pocisk w coś trafił? o_O Pocisk leci w pewnym kierunku z pewnymi parametrami i nie jest w żaden sposób od terrorysty zależny po wystrzeleniu!

0

No właśnie o to chodzi, że nie ingeruje w świecie rzeczywistym ale w programie jak zły
kod się napisze to może ingerować ;D

Jak to poprawnie za modelować ? Stworzyć jakąś klasę np ten kontroler lotu
i powiązać wszystkie bronie asocjacją z tym kontrolerem i jak będzie trzeba strzelić
to po prostu niech klasa broni odwoła się do kontrolera doda ten pocisk i kontroler
niech sobie kontroluje lot?

0

A czy pocisk sam nie może kontrolować swojego lotu? Pocisk to taki "agent" i sam generalnie sobie przecież radzi. Potrzebny jest co najwyżej obiekt do analizy zderzeń i tutaj nie ma rady, musisz mieć jakiś specjalny obiekt który wie o pozycjach obiektów i potrafi ocenić które się zderzyły.

1

Jak to poprawnie za modelować ? Stworzyć jakąś klasę np ten kontroler lotu
i powiązać wszystkie bronie asocjacją z tym kontrolerem i jak będzie trzeba strzelić
to po prostu niech klasa broni odwoła się do kontrolera doda ten pocisk i kontroler
niech sobie kontroluje lot?

Możesz wydzielić moduł fizyki, który steruje wszystkimi obiektami. Przy wystrzeleniu pocisku, jest tworzony obiekt fizyczny (ciało), którym zarządza moduł fizyki, który zarządza ruchem czy zderzeniami obiektów.

0

Napisałem sobie taki pseudokod:

int main() {

    vector<Pocisk*> pociski;
    for(int i=0; i<30; ++i) {
         pociski.push_back(new Pocisk);
    }

    Magazynek* magazynek = new Magazynek; magazynek->laduj(pociski);
    Bron* bron = new Bron; bron->set_magazynek(magazynek);

    Terrorysta* T; T = new Terrorysta;
    T->set_bron(bron);

    AntyTerrorysta* AT; AT = new AntyTerrorysta;
    AT->set_bron(new bron(*bron));
  
    while(true) {

     if(LPM == true) {

       AT->strzel_z_broni();

      }




     }

return 0;
} 

ale dalej nie wiem w jaki sposób mógłby być realizowany
lot tego pocisku. Jak ten pocisk sam miałby aktualizować
swoją pozycję jeżeli jest ukryty nisko i nie ma do niego
dostępu, metoda wystrzel() z broni ma być jakimś oddzielnym
wątkiem czy coś w tym stylu?

0

Nie ma jednego sposobu. Niemniej ja bym jednak użył tutaj czegoś innego. Niech każdy obiekt "aktywny" implementuje odpowiedni interfejs i ma metodę tick() która jest wołana na samej górze sceny i propagowana w dół do wszystkich obiektów. Ta metoda jest wołana co jeden "krok" i wtedy każdy obiekt wykonuje jeden krok swojej akcji.

0

Aha czyli ta metoda tick() byłaby taka do aktualizacji kaskadowo wszystkich obiektów
czyli AT->tick(); potem w tej metodzie coś tam aktualizuję oraz wywoluje bron->tick(); a ta z kolei wywoluje tick()
dla jakiegos aktywnego/wystrzelonego pocisku itd (?)

Poczytałem trochę o prg współbieżnym i jest ono wykorzystywane w większości dużych gier i aplikacji
więc dzieki temu chyba nie trzeba nisko się odwoływać do jakiś obiektów tylko tworzone są wątki aby zrealizować
aktualizację czy też jakieś sprawdzanie kolizji (?)
Wtedy jak postac strzeli z broni to nie wplywa już on na to jak ten pocisk leci tylko zajmuja sie tym klasy
podrzędne z wykorzystaniem wątków itp.

Jeżeli chciałbym do tego mojego modelu dodać, że jeżeli pocisk trafi w jakiś cel
to zwiększa się liczba punktów gracza to dodanie asocjacji obustronnej byloby dobrym pomysłem
(tj w pocisku jest wskaźnik na broń z której został wystrzelony a w broni wskaźnik na gracza, który strzelał)?

1
XiLk napisał(a):

Witam
Jestem początkującym programistą jeżeli chodzi o OOP (wcześniej pisałem strukturalnie).
Mam kilka pytań dotyczących projektowania/programowania obiektowego.
Pytania dotyczą aspektów nad którymi często się zastanawiam pisząc program
aby uzyskać dobrą czytelność jak najmniej powiązań itp. ponieważ uważam,
że OOP ułatwia budowanie aplikacji jeżeli się je dobrze zrozumie i umie wykorzystać.

1)
Czy w tego typu klasie:

class Punkt {

     public:
     Punkt(int x, int y);

     private:
     int x,y;
}

może być metoda: bool porownaj(const Punkt& p);

Może, a nawet powinna. W prawdziwych językach obiektowych metoda wirtualna equals jest zaimplementowana i domyślnie porównuje referencje, ale w wielu przypadkach się ją przeciąża, dzięki temu mogą sensownie działać np. wbudowane w język/BCL algorytmy sortowania czy struktury danych.

Jednak może takie rozumowanie
stosuję się bardziej przy większych aplikacjach z interfejsem użytkownika a w konsoli do
wyświetlenia dowolnego obiektu (np obiekt klasy Element, który przechowuje położenie int x,y; i char znak;)
przyjmuje się że taka metoda dla obiektu może być ?

Figura to element modelu dziedziny (czyli logiki biznesowej), zaś jej wyświetlanie to logika prezentacji. Nie ma żadnego powodu, aby mieszać jedno z drugim, bez względu na rodzaj aplikacji. Takie "ułatwianie" sobie życia prowadzi później do powstawania klas, które w konstruktorze pytają użytkownika o dane potrzebne do stworzenia siebie... Ani tego użyć gdzie indziej, ani przetestować sensownie.
Klasy, w których metoda wyświetl jest uzasadniona (a nawet wymagana) to jakieś kontrolki wizualne GUI, które same siebie potrafią rysować na ekranie.

Kiedy np mam klasę klasaA , która składa się z klasy klasaB a ta jeszcze się składa z klasaC
i potrzebuję informacji o czymś co jest przechowywane i udostępniane przez interfejs w klasie klasaC
to muszę dodać w klasach nadrzędnych(klasaB i klasaA) odpowiednie metody aby to zrobić?
Często jak potrzebuję takich informacji i myślę że tyle metod trzeba dodać aby to zrobić to wydaje
mi się, że coś jest źle zaprojektowane, albo obiekty są zbyt powiązane itp.

klasaB nie powinna uzewnętrzniać swojego wewnętrznego obiektu klasyC, a klasaA analogicznie nie powinna upubliczniać klasyB. Czyli nie piszesz tak:
oKlasaA.poleKlasaB.poleKlasaC.Metoda()
tylko
oKlasaA.Metoda()
która woła w sobie:
poleKlasaB.Metoda()
która woła w sobie:
poleKlasaC.Metoda()
To ma nawet swoją nazwę: https://pl.wikipedia.org/wiki/Prawo_Demeter

XiLk napisał(a):

Zbędne zależności to też jak graf powiązań
między klasami jest strasznie skomplikowany np jedna klasa ma wskaźniki do 3 innych lub więcej itp.

Trzy zależności w jednej klasie to raczej znacznie poniżej średniej.

Mam klasę AntyTerrorysta, który może strzelać z dowolnej broni a więc ma
wskaźnik na klasę abstrakcyjną broń. Broń ma wskaźnik na klasę magazynek
aktualnie w niej załadowany, a magazynek składa się z listy pocisków, które
można wystrzelić z broni.

A jaki jest sens istnienia klasy magazynek i listy pocisków? Program jest modelem rzeczywistości powstałym na potrzeby jej symulowania, nie jej dokładnym odwzorowaniem. To, że w rzeczywistości istnieją magazynki wypełnione pociskami, nie znaczy, że w programie komputerowym trzeba tak to realizować.
Czy nie prościej mieć w klasie broń licznik pocisków, zmniejszany za każdym wystrzałem, a pocisk jest dodawany do planszy gry, dopiero po wystrzale, czyli akcji użytkownika?

Jak teraz obsługiwać obiekty, które są tak nisko położone
w hierarchii np aktualnie wystrzelony pocisk, żeby np zmieniać jego położenie?
Mam napisać w każdej klasie metode get_wystrzelony_pocisk() i potem dodawać
je do jakiejś listy wystrzelonych pocisków? Czy mam tworzyć przysłowiowe 1000 metod
w klasie najwyższej (tj. Antyterrorysta) aby pracować na klasach niższych w hierarchii?
Po prostu jeżeli obiekty są tak poukrywane
to nie wiem jak na nich pracować.

Za bardzo komplikujesz, gracz, broń, magazynek, pocisk, to nie jest żadna hierarchia, żeby coś w tej strukturze hierarchicznie wywoływać.
Po prostu - obiekt reprezentujący gracza informuje obiekt "planszy" gry, że ma nastąpić wystrzał, do kolekcji ruchomych obiektów planszy dodawany jest nowy pocisk, jego początkowe współrzędne to wylot lufy broni.

XiLk napisał(a):

Jak to poprawnie za modelować ? Stworzyć jakąś klasę np ten kontroler lotu
i powiązać wszystkie bronie asocjacją z tym kontrolerem i jak będzie trzeba strzelić
to po prostu niech klasa broni odwoła się do kontrolera doda ten pocisk i kontroler
niech sobie kontroluje lot?

Nie jestem ekspertem, raz w życiu napisałem grę, i u mnie działa to tak, że klasa Silnik zawiera po prostu listę ruchomych elementów - zarówno obiektów tła, wrogów, jak i pocisków. Obiekty te mają parametryzowane kierunki ruchu, prędkości, rodzaj "zderzalności" (z graczem/z wrogiem/brak), wpływ na życie uderzanego obiektu, no i oczywiście własne punkty życia. Mój silnik w pętli, co ok 100ms wykonuje taki cykl czynności:

  1. usuwa martwe obiekty (za martwe uznaję też te elementy tła, które dotarły do granic widocznego obszaru);
  2. na każdym obiekcie ruchomym wywołuje jego metodę przesuń, która przesuwa ten obiekt na ekranie zgodnie z jego parametrami;
  3. prosi generator o wygenerowanie losowych obiektów (generator sam pilnuje, czy np. upłynął już minimalny możliwy czas, po jakim obiekt danego typu może się pojawić);
  4. generuje pociski, czyli: sprawdza stan broni gracza, jeśli gracz strzela, i upłynął wystarczający czas między pociskami, to do listy ruchomych obiektów silnikiem, dodawany jest nowy pocisk;
  5. wykrywa zderzenia (aktualizuje punkty życia trafionych obiektów, i tworzy nowe wybuchy)
  6. animuje trwające wybuchy.

Oczywiście, to prosty, jednowątkowy i całkowicie synchroniczny silnik, ale działa. I chyba lepiej zaczynać od czegoś takiego, nie od wątków.

0
somekind napisał(a):

A jaki jest sens istnienia klasy magazynek i listy pocisków?

Magazynki mogą mieć różną pojemność jak i różne typy pocisków, poza tym jak zrealizowałbyś np. odrzucenie magazynka?

0
Zimny Lew napisał(a):

Magazynki mogą mieć różną pojemność jak i różne typy pocisków

No ok. Nigdy nie widziałem czegoś takiego w grze 2D.

poza tym jak zrealizowałbyś np. odrzucenie magazynka?

Jako dłuższą przerwę między pociskami, gdy licznik nabojów w broni zejdzie do zera. Ale równie dobrze można wcale tego nie realizować.

0
somekind napisał(a):

No ok. Nigdy nie widziałem czegoś takiego w grze 2D.

Czyżby to ta słynna inowacyjność;)

Jako dłuższą przerwę między pociskami, gdy licznik nabojów w broni zejdzie do zera.

Pomijając to że to trochę perwersyjne rozwiązanie, jak go potem podniesiesz?

Ale równie dobrze można wcale tego nie realizować.

Jasne, jednak ta opcja jest stosunkowo prosta do realizacji, więc czemu nie?

0
somekind napisał(a):

Figura to element modelu dziedziny (czyli logiki biznesowej), zaś jej wyświetlanie to logika prezentacji. Nie ma żadnego powodu, aby mieszać jedno z drugim, bez względu na rodzaj aplikacji. Takie "ułatwianie" sobie życia prowadzi później do powstawania klas, które w konstruktorze pytają użytkownika o dane potrzebne do stworzenia siebie... Ani tego użyć gdzie indziej, ani przetestować sensownie.
Klasy, w których metoda wyświetl jest uzasadniona (a nawet wymagana) to jakieś kontrolki wizualne GUI, które same siebie potrafią rysować na ekranie.

Jak pisałem programy pod konsolę np jakieś gry to robiłem tak, że robiłem klasę Ekran, która odpowiedzialna była za pokazanie obiektu Item, który składał się z pozycji i znaku typu char w konsoli. Później każdy obiekt jaki stworzyłem podawałem mu referencje/wskaznik na ten ekran i dodawałem mu też metodę wyswietl, która właśnie wyświetlała z wykorzystaniem tego obiektu ekranu np

 
main() {

Screen screen;
Item* item = new Item(Point(10,10),'X');
item->setScreen(&screen);
item->show_item();

}

void Item::showItem() {

 screen->show_item(this);

}

I jak chciałbym zmienić np sposób wyświetlania to sobie zmieniam w klasie Ekran a
innych obiektów nie modyfikuje.

Wiem, że do rozdzielenia prezentacji od logiki programu służy model MVC.
Czy w każdej aplikacji dobrze jest go stosować? Np gry 2D 3D, aplikacje
z interfejsem użytkownika. Sugerujesz, że zawsze dobrze jest podzielić kod
czyli odseparować logikę od reprezentacji?

somekind napisał(a):

klasaB nie powinna uzewnętrzniać swojego wewnętrznego obiektu klasyC, a klasaA analogicznie nie powinna upubliczniać klasyB. Czyli nie piszesz tak:
oKlasaA.poleKlasaB.poleKlasaC.Metoda()

tylko
oKlasaA.Metoda()

która woła w sobie:
poleKlasaB.Metoda()

która woła w sobie:
poleKlasaC.Metoda()

Tak tę zasadę znam, klasy powinny hermetyzować co tylko się da
i udostępniać interfejs. Ogólnie uświadomiłem sobie, że klasa
postaci powinna mieć tylko te metody, które są zgodne
z jej poziomem abstrakcji czyli np skok(); strzel_z_broni();
wyrzuc_granat(); to jest nasz interfejs i nie obchodzi nas to
jak ta postac to robi, ważne, że umie to robić. Ale w środku tej
klasy oczywiście bedą metody, które np zmieniają pozycję broni
(my oczekujemy, że po wywolaniu metody skok(); automatycznie
zmieni się pozycja jego broni bo przecież ją trzyma). Od broni
oczekujemy, żeby umiała strzelać i dało się ją jakoś naładować.

Tylko nie wiem czesto jak takie wewnętrzne obiekty mają się
komunikować z innymi obiektami czy połączyć wskaźnikami
te obiekty (np analizuje i stwierdzam, że jeżeli obiektA zmienia
się na jakiś stan to coś się zmienia w jakimś tam obiekcieB z innej
klasy czyli od razu robię połączenie wskaźnikiem tych obiektów w projekcie)
czy może pytać te obiekty w jakim są stanie z wykorzystaniem klasy najwyższej
i wtedy wywoływać odpowiednie metody(co chyba raczej nie jest najlepszym pomysłem)
Ogólnie mam problem z takim tworzeniem spójnych modułów ponieważ albo wychodzi,
że klasa ma za dużo metod albo za dużo powiązań.

0

Czemu miałbym podnosić magazynek?

Jeszcze raz powtórzę - program jest modelem rzeczywistości, nie jej dokładnym odzwierciedleniem. Jeśli piszesz aplikacje do zarządzania sklepem warzywnym, to masz w niej klasę Towar z polami: Nazwa i Ilość zmniejszaną po wywołaniu metody RejestrujSprzedaż, a nie klasę Warzywo, dziedziczącą z niej Ziemniak, i klasę Worek z listą Ziemniaków, które wyciąga obiekt klasy Sprzedawca przy użyciu obiektu klasy Ręka.

Ja pisałem o najprostszym przypadku, w którym istnienie klasy magazynek nie ma sensu - bo wszystko da się załatwić polami klasy broń, nawet animowanie wyrzucenia magazynku nie wymaga istnienia oddzielnej klasy do tego.
Oczywiście można napisać grę, w której magazynki się zbiera, wymienia, a nawet uzupełnia, wtedy istnienie takiej klasy jest uzasadnione. Ale nadal wystarczy, że będzie miała pole liczbaNabojów, nie trzeba mieć listy obiektów klasy pocisk.

XiLk napisał(a):

Wiem, że do rozdzielenia prezentacji od logiki programu służy model MVC.
Czy w każdej aplikacji dobrze jest go stosować? Np gry 2D 3D, aplikacje
z interfejsem użytkownika. Sugerujesz, że zawsze dobrze jest podzielić kod
czyli odseparować logikę od reprezentacji?

Taka separacja jest zawsze dobra, ale MVC nie jest konieczny. Są inne wzorce - MVVM, MVP. Nie wiem, które z nich są dobre do gier, podejrzewam, że żaden - bo gra to jednak znacznie się różni od innych aplikacji.

Ogólnie mam problem z takim tworzeniem spójnych modułów ponieważ albo wychodzi,
że klasa ma za dużo metod albo za dużo powiązań.

A po czym wnosisz, że klasa ma za dużo tego, czy tamtego? Bo może przejmujesz się na wyrost.

0
somekind napisał(a):

A po czym wnosisz, że klasa ma za dużo tego, czy tamtego? Bo może przejmujesz się na wyrost.

Po prostu czuję, że coś jest nie tak np dodaję metodę do klasy po to aby zwróciła mi jakieś info o obiekcie głęboko w hierarchii kompozycji tylko po to aby przekazać te info do innej klasy, albo wywoluje metodę na jakims obiekcie która zmienia też inny obiekt ale zmiana tego obiektu powoduje zmiane następnego itd taki lancuszek powstaje i wychodzi na to ze wszystko zalezy od wszystkiego.
A może nie powinno się wiązać klasy jako komponent(moze byc asocjacja itp) w klasie nadrzędnej(kompozyt) jeżeli ten komponent zamiast służyć pomocą dla kompozytu(wziąc na siebie jakąś odpowiedzialność) służy tylko do zmiany jego stanu itp.

Czy poprawne jest taka konfiguracja: klasaA, ktora korzysta z klasy klasaB(nieważne czy asocjacja czy kompozycja) i klasaB jest dostępna w programie głównym(w funkcji main, w której korzystam z klas, które wcześniej napisałem)?
Czy strzałki kolorowe na obrazku reprezentujące powiązania są poprawne?

0

Z diagramu który rzuciłeś nic nie wynika, nieznana jest odpowiedzialność klas, kierunek przepływu danych i ich typ. Podsumowując, jeśli to ma sens to ma to sens a jeśli nie ma sensu to jest bezsensu.

To jest diagram ogólny tylko, klasa do której prowadzi strzałka mówi, że wykorzystuję tę klasę do obliczeń/korzystam z niej.
Czyli tam gdzie strzałka się zaczyna to znaczy, że mam instancje(wskaźnik/referencja) tej klasy do której ta strzałka prowadzi.
Podobnie było w książce Symfonia C++(tzw graf wspolpracy obiektow, czyli widzimy, które obiekty wydają polecenia innym).
Powinienem w sumie tam wpisać object a nie class.

I teraz czy te powiązania, które zaznaczyłem kolorem są poprawne? Czy może być np tak, że obiekt z prawej strony może korzystać z obiektu z lewej strony(niebieska strzałka) i jednocześnie ten obiekt jest używany w main? Czy można tak tworzyć obiekty, że jednocześnie korzysta z nich wiele innych obiektów na różnych poziomach abstrakcji?

0

Sprawdzi ktoś ten diagram żeby już zamknąć temat ;)

1

Diagram olej - zbyt wiele z niego nie wynika. Zresztą to czy dany projekt jest dobry czy zły to akurat kwestia dość subiektywna ;)
Prawda jest taka, że mógłbyś tą grę napisać w postaci jednej, ultradługiej funkcji i - przy odrobinie szczęścia - też by działała.
Kod piszesz dla siebie - żeby sprawnie wprowadzać poprawki, dodawać nowe funkcje, ogarniać umysłem co się tam w ogóle dzieje. I tylko od Ciebie zależy czy z funkcji x() możesz odwołać się do obiektu Y. Dlatego jeśli podczas pisania programu czujesz, że coś robisz wbrew sobie to zastanów się jak można to zrobić lepiej, sprawniej, bardziej elegancko - i tak to właśnie zapisz. Refaktoryzuj dowoli :)

0
sihox napisał(a):

Prawda jest taka, że mógłbyś tą grę napisać w postaci jednej, ultradługiej funkcji i - przy odrobinie szczęścia - też by działała.
Kod piszesz dla siebie - żeby sprawnie wprowadzać poprawki, dodawać nowe funkcje, ogarniać umysłem co się tam w ogóle dzieje.

Jak bym był programistą gier to szef raczej nie byłby zadowolony z takiego kodu :D
Ok temat już można zamknąć, sporo mi się wyjaśniło dzięki za odpowiedzi wszystkim ;)

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