Martwił go, że duża liczba zajęć przełoży się na koszmar utrzymania. Moim zdaniem byłby to dokładnie odwrotny skutek.
Jestem absolutnie po stronie twojego przyjaciela, ale może to być kwestia naszych domen i rodzajów problemów i projektów, które rozwiązujemy, a zwłaszcza tego, jakie rodzaje rzeczy mogą wymagać zmian w przyszłości. Różne problemy, różne rozwiązania. Nie wierzę w dobro ani zło, po prostu programiści starają się znaleźć najlepszy sposób, aby najlepiej rozwiązać swoje problemy projektowe. Pracuję w VFX, który nie jest zbyt podobny do silników gier.
Ale problem, z którym borykam się w czymś, co można by nazwać nieco bardziej architekturą zgodną z SOLID (opartą na modelu COM), można z grubsza sprowadzić do „zbyt wielu klas” lub „zbyt wielu funkcji”, ponieważ twój przyjaciel może to opisać. Powiedziałbym konkretnie: „zbyt wiele interakcji, zbyt wiele miejsc, które mogą być niewłaściwie zachowane, zbyt wiele miejsc, które mogą powodować skutki uboczne, zbyt wiele miejsc, które mogą wymagać zmiany, i zbyt wiele miejsc, które mogą nie robić tego, co naszym zdaniem robią . ”
Mieliśmy garść abstrakcyjnych (i czystych) interfejsów zaimplementowanych przez mnóstwo różnych podtypów, takich jak ten (stworzyliśmy ten diagram w kontekście mówienia o korzyściach ECS, zignoruj lewy dolny komentarz):
Gdzie interfejs ruchu lub interfejs węzła sceny mogą być implementowane przez setki podtypów: światła, kamery, siatki, solwery fizyki, shadery, tekstury, kości, prymitywne kształty, krzywe itp. Itp. (I często istniało wiele rodzajów każdego z nich ). Ostatecznym problemem było to, że projekty te nie były tak stabilne. Mieliśmy zmieniające się wymagania, a czasem same interfejsy musiały się zmienić, a kiedy chcesz zmienić abstrakcyjny interfejs zaimplementowany przez 200 podtypów, jest to niezwykle kosztowna zmiana. Zaczęliśmy to łagodzić, stosując abstrakcyjne klasy podstawowe, między którymi zmniejszały się koszty takich zmian projektowych, ale były one nadal drogie.
Alternatywnie zacząłem badać architekturę systemu encja-komponent, stosowaną raczej powszechnie w branży gier. To zmieniło wszystko tak:
I wow! To była taka różnica pod względem łatwości konserwacji. Zależności nie płynęły już w kierunku abstrakcji , ale w kierunku danych (komponentów). I przynajmniej w moim przypadku dane były znacznie bardziej stabilne i łatwiejsze do poprawnego zaprojektowania, pomimo zmieniających się wymagań (chociaż to, co możemy zrobić z tymi samymi danymi, ciągle się zmienia wraz ze zmieniającymi się wymaganiami).
Ponieważ jednostki w ECS używają kompozycji zamiast dziedziczenia, tak naprawdę nie muszą zawierać funkcji. Są tylko analogicznym „pojemnikiem komponentów”. Dzięki temu analogiczne 200 podtypów, które implementowały interfejs ruchu , zamieniają się w 200 instancji encji (nie oddzielne typy z osobnym kodem), które po prostu przechowują komponent ruchu (który jest niczym innym jak danymi związanymi z ruchem). A PointLight
nie jest już osobną klasą / podtypem. To wcale nie jest klasa. Jest to przykład bytu, który po prostu łączy niektóre komponenty (dane) związane z tym, gdzie jest w przestrzeni (ruch) i określone właściwości świateł punktowych. Jedyną związaną z nimi funkcją jest system, taki jakRenderSystem
, który szuka lekkich komponentów w scenie, aby określić sposób renderowania sceny.
Wraz ze zmieniającymi się wymaganiami w ramach podejścia ECS często trzeba było zmienić tylko jeden lub dwa systemy działające na tych danych lub po prostu wprowadzić nowy system z boku lub wprowadzić nowy komponent, jeśli potrzebne byłyby nowe dane.
Tak więc przynajmniej dla mojej domeny i jestem prawie pewien, że nie dla wszystkich, to znacznie ułatwiło sprawę, ponieważ zależności płynęły w kierunku stabilności (rzeczy, które wcale nie musiały się często zmieniać). Tak nie było w architekturze COM, gdy zależności jednorodnie płynęły w kierunku abstrakcji. W moim przypadku o wiele łatwiej jest dowiedzieć się, jakie dane są wymagane do ruchu z góry, niż wszystkie możliwe rzeczy, które możesz z tym zrobić, co często zmienia się nieco w ciągu miesięcy lub lat, gdy pojawiają się nowe wymagania.
Czy w OOP są przypadki, w których niektóre lub wszystkie zasady SOLID nie nadają się do czyszczenia kodu?
Cóż, czystego kodu nie mogę powiedzieć, ponieważ niektórzy ludzie utożsamiają czysty kod z SOLID, ale zdecydowanie są pewne przypadki, w których oddzielanie danych od funkcjonalności, podobnie jak ECS, i przekierowywanie zależności od abstrakcji w kierunku danych zdecydowanie może znacznie ułatwić zmień, z oczywistych powodów sprzężenia, jeśli dane będą znacznie bardziej stabilne niż abstrakcje. Oczywiście zależności od danych mogą utrudniać utrzymanie niezmienników, ale ECS ma tendencję do ograniczania tego do minimum dzięki organizacji systemu, która minimalizuje liczbę systemów, które uzyskują dostęp do dowolnego typu elementu.
Zależności nie muszą koniecznie płynąć w kierunku abstrakcji, jak sugerowałby DIP; zależności powinny płynąć w kierunku rzeczy, które prawdopodobnie nie będą wymagały przyszłych zmian. To mogą być lub nie abstrakcje we wszystkich przypadkach (z pewnością nie były moje).
- Tak, istnieją zasady projektowania OOP, które częściowo kolidują z SOLID
- Tak, istnieją zasady projektowania OOP, które są całkowicie sprzeczne z SOLID.
Nie jestem pewien, czy ECS jest naprawdę smakiem OOP. Niektórzy ludzie definiują to w ten sposób, ale widzę, że jest to zupełnie odmienne z natury charakterystyka sprzężenia i oddzielenie danych (komponentów) od funkcjonalności (systemów) i braku enkapsulacji danych. Gdyby uznać to za formę OOP, pomyślałbym, że jest to w dużym stopniu sprzeczne z SOLID (przynajmniej najostrzejsze idee SRP, open / closed, substytucja Liskova i DIP). Mam jednak nadzieję, że jest to rozsądny przykład jednego przypadku i dziedziny, w której najbardziej fundamentalne aspekty SOLID, przynajmniej tak, jak ludzie na ogół interpretują je w bardziej rozpoznawalnym kontekście OOP, mogą nie mieć zastosowania.
Teeny Classes
Wyjaśniałem architekturę jednej z moich gier, która, ku zaskoczeniu mojego przyjaciela, zawierała wiele małych klas i kilka warstw abstrakcji. Argumentowałem, że był to wynik mojego skupienia się na nadaniu wszystkim Jednej Odpowiedzialności, a także na poluzowaniu sprzężenia między komponentami.
ECS podważyło i zmieniło moje poglądy. Podobnie jak ty, myślałem, że samą ideą łatwości utrzymania jest najprostsza implementacja rzeczy możliwych, co implikuje wiele rzeczy, a ponadto wiele współzależnych rzeczy (nawet jeśli współzależności występują między abstrakcjami). Jest to najbardziej sensowne, jeśli przybliżasz tylko jedną klasę lub funkcję, aby zobaczyć najprostszą i najprostszą implementację, a jeśli jej nie widzimy, przebuduj ją, a może nawet rozłóż. Ale w rezultacie łatwo może przeoczyć to, co dzieje się ze światem zewnętrznym, ponieważ za każdym razem, gdy dzielisz coś stosunkowo złożonego na 2 lub więcej rzeczy, te 2 lub więcej rzeczy musi nieuchronnie oddziaływać * (patrz poniżej) w niektórych sposób, albo coś na zewnątrz musi wchodzić w interakcję z nimi wszystkimi.
W dzisiejszych czasach uważam, że istnieje równowaga między prostotą czegoś a ilością rzeczy i wymaganą interakcją. Systemy w ECS wydają się być dość mocny z nietrywialnych wdrożeń działać na danych, jak PhysicsSystem
albo RenderSystem
albo GuiLayoutSystem
. Jednak fakt, że tak skomplikowany produkt potrzebuje tak niewielu, ułatwia cofnięcie się i uzasadnienie ogólnego zachowania całej bazy kodu. Jest w tym coś, co może sugerować, że nie jest złym pomysłem oparcie się na mniejszej liczbie, bardziej masywnych klas (wciąż wykonujących prawdopodobnie osobliwą odpowiedzialność), jeśli oznacza to mniej klas do utrzymania i uzasadnienia oraz mniej interakcji w całym tekście system.
Interakcje
Mówię „interakcje”, a nie „łączenie” (choć redukcja interakcji oznacza redukcję obu), ponieważ można użyć abstrakcji, aby rozdzielić dwa konkretne obiekty, ale one nadal ze sobą rozmawiają. Nadal mogą powodować działania niepożądane w procesie tej pośredniej komunikacji. I często uważam, że moja zdolność do rozumowania poprawności systemu jest bardziej związana z tymi „interakcjami” niż z „sprzężeniem”. Minimalizowanie interakcji znacznie ułatwia mi rozumowanie wszystkiego z lotu ptaka. Oznacza to, że rzeczy w ogóle się ze sobą nie rozmawiają, i z tego punktu widzenia ECS ma tendencję do naprawdę minimalizowania „interakcji”, a nie tylko łączenia, do najmniejszego minimum (przynajmniej ja nie
To powiedziawszy, może to być przynajmniej częściowo ja i moje osobiste słabości. Znalazłem największą przeszkodę dla mnie, aby stworzyć systemy o ogromnej skali, i nadal pewnie o nich myślę, nawigować po nich i czuję, że mogę wprowadzić wszelkie potencjalnie pożądane zmiany w dowolnym miejscu w przewidywalny sposób, zarządzanie stanem i zasobami oraz skutki uboczne. To największa przeszkoda, która zaczyna się pojawiać, gdy przechodzę od dziesiątek tysięcy LOC do setek tysięcy LOC do milionów LOC, nawet dla kodu, który sam stworzyłem. Jeśli coś spowolni mnie do czołgania się przede wszystkim, mam wrażenie, że nie mogę już zrozumieć, co się dzieje pod względem stanu aplikacji, danych, skutków ubocznych. To' to nie czas robota, którego wymaga wprowadzenie zmian, które mnie spowalniają, tak samo jak niemożność zrozumienia pełnego wpływu zmiany, jeśli system wykracza poza zdolność rozumowania o tym. A zmniejszenie interakcji było dla mnie najskuteczniejszym sposobem na zwiększenie produktu o wiele więcej funkcji, przy czym osobiście nie jestem przytłoczony tymi rzeczami, ponieważ ograniczenie interakcji do minimum również zmniejsza liczbę miejsc, które mogą może nawet zmienić stan aplikacji i spowodować znaczne skutki uboczne.
Może zmienić coś w ten sposób (gdzie wszystko na diagramie ma funkcjonalność, i oczywiście scenariusz w świecie rzeczywistym miałby wiele, wiele razy więcej obiektów, a jest to diagram „interakcji”, a nie sprzężony, jako sprzężenie pomiędzy nimi byłyby abstrakcje):
... do tego, gdzie funkcjonują tylko systemy (niebieskie komponenty są teraz tylko danymi, a teraz jest to schemat sprzęgania):
Pojawiają się przemyślenia na ten temat i być może sposób na ujęcie niektórych z tych korzyści w bardziej zgodny kontekst OOP, który jest bardziej kompatybilny z SOLID, ale jeszcze nie do końca znalazłem projekty i słowa, i znajduję to trudne, ponieważ terminologia była przyzwyczajona do rzucania wszystkimi związanymi bezpośrednio z OOP. Próbuję to rozgryźć, czytając odpowiedzi ludzi tutaj, a także staram się sformułować własne, ale są pewne rzeczy bardzo interesujące w naturze ECS, których nie byłem w stanie idealnie położyć na tym palca może mieć szersze zastosowanie nawet do architektur, które go nie używają. Mam również nadzieję, że ta odpowiedź nie wyjdzie jako promocja ECS! Uważam to za bardzo interesujące, ponieważ projektowanie ECS naprawdę zmieniło moje myśli drastycznie,