Mam podstawową grę obrony wieży 2D w C ++.
Każda mapa jest oddzielną klasą, która dziedziczy po GameState. Mapa deleguje logikę i kod rysunkowy do każdego obiektu w grze i ustawia dane, takie jak ścieżka mapy. W pseudokodzie sekcja logiki może wyglądać mniej więcej tak:
update():
for each creep in creeps:
creep.update()
for each tower in towers:
tower.update()
for each missile in missiles:
missile.update()
Obiekty (skrada się, wieże i pociski) są przechowywane w wektorze wskaźników. Wieże muszą mieć dostęp do wektora pełzania i wektora pocisków, aby tworzyć nowe pociski i identyfikować cele.
Pytanie brzmi: gdzie mam zadeklarować wektory? Czy powinni należeć do klasy Map i przekazywani jako argumenty do funkcji tower.update ()? Czy zadeklarowany globalnie? Czy są też inne rozwiązania, których zupełnie mi brakuje?
Kiedy kilka klas potrzebuje dostępu do tych samych danych, gdzie należy je zadeklarować?
źródło
Odpowiedzi:
Gdy potrzebujesz jednego wystąpienia klasy w całym programie, nazywamy tę klasę usługą . Istnieje kilka standardowych metod wdrażania usług w programach:
Singletony . Około 10-15 lat temu, singletons były duży projekt, wzór wiedzieć. Jednak w dzisiejszych czasach patrzy się na nie z góry. Są one znacznie łatwiejsze do wielowątkowego, ale musisz ograniczyć ich użycie do jednego wątku na raz, co nie zawsze jest tym, czego chcesz. Śledzenie żywotności jest tak samo trudne, jak w przypadku zmiennych globalnych. Typowa klasa singleton będzie wyglądać mniej więcej tak:
Wstrzykiwanie zależności (DI) . Oznacza to po prostu przekazanie usługi jako parametru konstruktora. Usługa musi już istnieć, aby przenieść ją do klasy, więc nie można polegać na dwóch usługach; w 98% przypadków tego właśnie chcesz (a dla pozostałych 2% zawsze możesz utworzyć
setWhatever()
metodę i przekazać ją później) . Z tego powodu DI nie ma takich samych problemów ze sprzężeniem, jak inne opcje. Można go używać z wielowątkowością, ponieważ każdy wątek może po prostu mieć własną instancję każdej usługi (i udostępniać tylko te, których absolutnie potrzebuje). Umożliwia to również testowanie jednostki kodu, jeśli na tym zależy.Problem z wstrzykiwaniem zależności polega na tym, że zajmuje więcej pamięci; teraz każda instancja klasy potrzebuje referencji do każdej usługi, z której będzie korzystać. Korzystanie z zbyt wielu usług jest denerwujące. istnieją frameworki, które łagodzą ten problem w innych językach, ale z powodu braku refleksji w C ++, frameworki DI w C ++ wydają się być jeszcze więcej pracy niż tylko ręczne tworzenie.
Zobacz tę stronę (z dokumentacji Ninject, frameworka C # DI) dla innego przykładu.
Wstrzykiwanie zależności jest typowym rozwiązaniem tego problemu i jest odpowiedzią, którą zobaczysz najbardziej wysoko postawiony na takie pytania na StackOverflow.com. DI jest rodzajem Inwersji Kontroli (IoC).
Lokalizator usług . Zasadniczo, tylko klasa, która przechowuje instancję każdej usługi. Możesz to zrobić za pomocą refleksji lub możesz po prostu dodać do niej nową instancję za każdym razem, gdy chcesz utworzyć nową usługę. Nadal masz ten sam problem, co wcześniej - w jaki sposób klasy uzyskują dostęp do tego lokalizatora? - które można rozwiązać na jeden z powyższych sposobów, ale teraz musisz to zrobić tylko dla swojej
ServiceLocator
klasy, a nie dla dziesiątek usług. Ta metoda jest również testowalna jednostkowo, jeśli zależy Ci na tego rodzaju rzeczach.Lokalizatory usług to kolejna forma Inwersji Kontroli (IoC). Zazwyczaj środowiska wykonujące automatyczne wstrzykiwanie zależności będą miały także lokalizator usług.
XNA (platforma programowania gier C # firmy Microsoft) zawiera lokalizator usług; Aby dowiedzieć się więcej na ten temat, zobacz tę odpowiedź .
Nawiasem mówiąc, wieże IMHO nie powinny wiedzieć o stworach. O ile nie planujesz po prostu przewijać listy pełzaczy dla każdej wieży, prawdopodobnie będziesz chciał wdrożyć nietrywialne partycjonowanie przestrzeni ; i tego rodzaju logika nie należy do klasy wież.
źródło
Osobiście użyłbym tutaj polimorfizmu. Po co
missile
wektor,tower
wektor icreep
wektor ... kiedy wszystkie wywołują tę samą funkcję;update
? Dlaczego nie mieć wektora wskaźników do jakiejś klasy bazowejEntity
lubGameObject
?Uważam, że dobrym sposobem na projektowanie jest myślenie „czy to ma sens pod względem własności”? Oczywiście wieża ma sposób na aktualizację, ale czy mapa posiada wszystkie obiekty na niej? Jeśli wybierasz globalny, to czy mówisz, że nic nie jest właścicielem wież i skrada się? Globalne jest zwykle złym rozwiązaniem - promuje złe wzorce projektowe, ale znacznie łatwiej jest z nim pracować. Zastanów się nad ważeniem „czy chcę to zakończyć?” i „czy chcę czegoś, co mogę ponownie wykorzystać”?
Jednym ze sposobów obejścia tego jest jakaś forma systemu przesyłania wiadomości.
tower
Może wysłać wiadomość domap
(których ma dostęp, być może odniesienie do jego właściciela?), Że to hitcreep
, amap
następnie opowiadacreep
to był hit. Jest to bardzo czyste i segreguje dane.Innym sposobem jest po prostu przeszukiwanie samej mapy w poszukiwaniu tego, czego chce. Mogą tu jednak występować problemy z kolejnością aktualizacji.
źródło
Jest to przypadek, w którym rozpada się ścisłe programowanie obiektowe (OOP).
Zgodnie z zasadami OOP należy grupować dane z powiązanymi zachowaniami za pomocą klas. Ale masz zachowanie (targetowanie), które potrzebuje danych, które nie są ze sobą powiązane (wieże i stwory). W tej sytuacji wielu programistów będzie próbowało powiązać zachowanie z częścią potrzebnych danych (np. Wieże obsługują celowanie, ale nie wiedzą o pełzaniu), ale jest też inna opcja: nie grupuj zachowania z danymi.
Zamiast uczynić zachowanie targetowania metodą klasy tower, uczyń z niego bezpłatną funkcję, która przyjmuje wieże i pełznie jako argumenty. Może to wymagać upublicznienia większej liczby członków, którzy pozostali w wieży i klas pełzania . Ukrywanie danych jest przydatne, ale jest środkiem, a nie celem samym w sobie i nie powinieneś być do niego niewolnikiem. Poza tym prywatni członkowie nie są jedynym sposobem kontrolowania dostępu do danych - jeśli dane nie są przekazywane do funkcji i nie są globalne, są skutecznie ukryte przed tą funkcją. Jeśli użycie tej techniki pozwala uniknąć globalnych danych, być może poprawia się enkapsulacja.
Skrajnym przykładem tego podejścia jest architektura systemu encji .
źródło