Czy źle jest mieć 2 klasy, które się potrzebują?
Piszę małą grę, w której mam GameEngine
klasę, która ma kilka GameState
przedmiotów. Aby uzyskać dostęp do kilku metod renderowania, GameState
obiekty te muszą również znać GameEngine
klasę - więc jest to zależność cykliczna.
Czy nazwałbyś to złym projektem? Po prostu pytam, ponieważ nie jestem do końca pewien i w tej chwili jestem w stanie refaktoryzować te rzeczy.
c++
architecture
shad0w
źródło
źródło
Odpowiedzi:
Z natury nie jest to zły projekt, ale łatwo można wymknąć się spod kontroli. W rzeczywistości używasz tego projektu w swoich codziennych kodach. Na przykład wektor wie, że jest to pierwszy iterator, a iteratory mają wskaźnik do kontenera.
W twoim przypadku znacznie lepiej jest mieć dwie osobne klasy dla GameEnigne i GameState. Ponieważ w zasadzie ci dwaj robią różne rzeczy, a później możesz zdefiniować wiele klas, które dziedziczą GameState (jak GameState dla każdej sceny w grze). I nikt nie może zaprzeczyć ich potrzebie dostępu do siebie nawzajem. Zasadniczo GameEngine obsługuje gamestaty, więc powinien mieć do nich wskaźnik. GameState korzysta z zasobów zdefiniowanych w GameEngine (takich jak renderowany, menedżer fizyki itp.).
Nie możesz i nie powinieneś łączyć ze sobą tych dwóch klas, ponieważ z natury robią różne rzeczy, a łączenie spowoduje bardzo dużą klasę, której nikt nie lubi.
Jak dotąd wiemy, że potrzebujemy okrągłej zależności w naszym projekcie. Istnieje wiele sposobów bezpiecznego tworzenia:
Kończąc jego odpowiedź, możesz użyć jednej z tych trzech metod, ale musisz uważać, aby nie użyć zbytnio żadnej z nich, ponieważ, jak powiedziałem, wszystkie te projekty są naprawdę niebezpieczne i mogą łatwo doprowadzić do niemożliwego do utrzymania kodu.
źródło
To trochę Zapach Kodowy , ale można z tym wyjść. Jeśli jest to łatwiejszy i szybszy sposób na uruchomienie gry, wybierz ją. Ale pamiętaj o tym, ponieważ istnieje duża szansa, że w pewnym momencie będziesz musiał to zmienić.
C ++ polega na tym, że zależności cykliczne nie będą się tak łatwo kompilować , więc lepszym pomysłem może się ich pozbyć zamiast spędzać czas na naprawianiu kompilacji.
Zobacz to pytanie na SO, aby uzyskać więcej opinii.
Nie, to wciąż lepsze niż umieszczanie wszystkiego w jednej klasie.
Nie jest tak świetny, ale w rzeczywistości jest bardzo zbliżony do większości wdrożeń, jakie widziałem. Zwykle masz klasę menedżera stanów gry ( strzeż się! ) I klasę renderera i dość często są to singletony. Tak więc zależność cykliczna jest „ukryta”, ale potencjalnie istnieje.
Ponadto, jak powiedziano w komentarzach, to trochę dziwne, że klasy stanu gry wykonują jakiś rendering. Powinny one po prostu przechowywać informacje o stanie, a rendering powinien być obsługiwany przez renderer lub jakiś element graficzny samych obiektów gry.
Teraz może być ostateczny projekt. Jestem ciekawy, czy inne odpowiedzi przynoszą jeden fajny pomysł. Mimo to prawdopodobnie jesteś w stanie znaleźć najlepszy projekt dla Twojej gry.
źródło
Często uważa się, że zły projekt musi mieć 2 klasy, które bezpośrednio się do siebie odnoszą, tak. W praktyce śledzenie przepływu sterowania przez kod może być trudniejsze, własność obiektów i ich czas życia mogą być skomplikowane, co oznacza, że żadna klasa nie nadaje się do ponownego użycia bez drugiej, może to oznaczać, że przepływ sterowania powinien naprawdę żyć poza oboma z tych klas w trzeciej klasie „mediatora” i tak dalej.
Jednak bardzo często dwa obiekty odnoszą się do siebie, a różnica polega na tym, że zwykle relacja w jednym kierunku jest bardziej abstrakcyjna. Na przykład w systemie Model-View-Controller obiekt View może zawierać odwołanie do obiektu Model i będzie wiedział o nim wszystko, będąc w stanie uzyskać dostęp do wszystkich swoich metod i właściwości, aby widok mógł wypełnić się odpowiednimi danymi . Obiekt Model może przechowywać odniesienie z powrotem do Widoku, aby mógł dokonać aktualizacji Widoku po zmianie własnych danych. Ale zamiast modelu z odniesieniem do widoku - co uzależniałoby model od widoku - zwykle widok implementuje interfejs obserwowalny, często z tylko 1
Update()
funkcja, a model zawiera odniesienie do obserwowalnego obiektu, którym może być widok. Kiedy Model się zmienia, wywołujeUpdate()
wszystkie swoje Obserwowalne, a widok implementujeUpdate()
, oddzwaniając do Modelu i pobierając wszelkie zaktualizowane informacje. Zaletą jest to, że Model w ogóle nie wie nic o Widoku (i dlaczego miałby to robić?) I może być ponownie użyty w innych sytuacjach, nawet tych bez Widoku.W swojej grze masz podobną sytuację. GameEngine zwykle będzie wiedział o GameStates. Ale GameState nie musi wiedzieć wszystkiego o GameEngine - potrzebuje jedynie dostępu do niektórych metod renderowania w GameEngine. To powinno wywołać mały alarm w twojej głowie, który mówi, że albo (a) GameEngine próbuje zrobić zbyt wiele rzeczy w obrębie jednej klasy i / lub (b) GameState nie potrzebuje całego silnika gry, tylko część do renderowania.
Trzy podejścia do rozwiązania tego problemu obejmują:
źródło
Powszechnie uważa się, że dobrą praktyką jest wysoka kohezja przy niskim sprzężeniu. Oto kilka linków dotyczących sprzężenia i spójności.
sprzęganie wikipedia
spójność wikipedia
niskie sprzęgło, wysoka kohezja
przepływ stosów zgodnie z najlepszymi praktykami
Posiadanie dwóch klas do siebie nawzajem oznacza wysokie sprzężenie. W google ramowe Guice ma na celu uzyskanie wysokiej spójności z niską sprzęgania przez środki wtryskiwania zależności. Sugerowałbym, abyś przeczytał nieco więcej na ten temat, a następnie wykonał własną rozmowę, biorąc pod uwagę kontekst.
źródło