Wielopostaciowość
Tak długo, jak używasz getType()
lub coś podobnego, nie używasz polimorfizmu.
Rozumiem, że musisz wiedzieć, jaki masz typ. Ale każda praca, którą chciałbyś wykonać, wiedząc, że naprawdę powinna zostać zepchnięta na zajęcia. Następnie po prostu powiedz, kiedy to zrobić.
Kod proceduralny otrzymuje informacje, a następnie podejmuje decyzje. Kod zorientowany obiektowo każe obiektom robić rzeczy.
- Alec Sharp
Ta zasada nazywa się powiedz, nie pytaj . Postępowanie zgodnie z tym pomaga ci nie rozpowszechniać szczegółów takich jak pisanie i tworzenie logiki, która na nie działa. Dzięki temu klasa wywraca się na lewą stronę. Lepiej jest zachować to zachowanie w klasie, aby mogło się zmienić, gdy klasa się zmieni.
Kapsułkowanie
Możesz mi powiedzieć, że żadne inne kształty nigdy nie będą potrzebne, ale ja ci nie wierzę i ty też nie powinieneś.
Dobrym efektem następującego po enkapsulacji jest to, że łatwo jest dodawać nowe typy, ponieważ ich szczegóły nie rozprzestrzeniają się w kodzie, w którym pojawiają się if
i switch
logika. Kod nowego typu powinien znajdować się w jednym miejscu.
Nieświadomy typ systemu wykrywania kolizji
Pozwól, że pokażę ci, jak zaprojektowałbym skuteczny system wykrywania kolizji, który działa z dowolnym kształtem 2D, nie dbając o typ.
Powiedz, że miałeś to narysować. Wydaje się proste. To wszystko krąży. Kuszące jest stworzenie klasy kręgu, która rozumie kolizje. Problem polega na tym, że przesuwa nas myślenie, które rozpada się, gdy potrzebujemy 1000 kół.
Nie powinniśmy myśleć o kręgach. Powinniśmy myśleć o pikselach.
Co, jeśli powiem ci, że ten sam kod, którego używasz do rysowania tych facetów, jest tym, czego możesz użyć do wykrycia, kiedy się dotykają, a nawet tego, który użytkownik klika.
Tutaj narysowałem każdy okrąg unikalnym kolorem (jeśli twoje oczy są wystarczająco dobre, aby zobaczyć czarny kontur, po prostu zignoruj to). Oznacza to, że każdy piksel tego ukrytego obrazu odwzorowuje z powrotem na to, co go narysował. Haszapa ładnie się tym zajmuje. W ten sposób możesz faktycznie robić polimorfizm.
Ten obraz, którego nigdy nie musisz pokazywać użytkownikowi. Tworzysz go za pomocą tego samego kodu, który narysował pierwszy. Tylko w różnych kolorach.
Gdy użytkownik kliknie koło, wiem dokładnie, które koło, ponieważ tylko jedno koło ma ten kolor.
Kiedy narysuję okrąg na innym, mogę szybko odczytać każdy piksel, który zamierzam zastąpić, zrzucając go do zestawu. Kiedy skończę, ustawiam punkty dla każdego koła, z którym się zderzyło, a teraz muszę tylko zadzwonić do każdego z nich, aby powiadomić go o kolizji.
Nowy typ: prostokąty
Wszystko to wykonano za pomocą kół, ale pytam: czy działałoby inaczej z prostokątami?
Żadna wiedza z kręgów nie wyciekła do systemu detekcji. Nie obchodzi go promień, obwód ani punkt środkowy. Dba o piksele i kolor.
Jedyną częścią tego systemu kolizji, który należy docisnąć do poszczególnych kształtów, jest unikalny kolor. Poza tym kształty mogą myśleć o rysowaniu kształtów. W każdym razie są w tym dobrzy.
Teraz, kiedy piszesz logikę kolizji, nie obchodzi cię, jaki masz podtyp. Mówisz mu, żeby się zderzył, i mówi ci, co znalazł pod kształtem, który udaje, że rysuje. Nie musisz znać typu. A to oznacza, że możesz dodać tyle podtypów, ile chcesz, bez konieczności aktualizacji kodu w innych klasach.
Opcje realizacji
Naprawdę nie musi to być unikalny kolor. Mogą to być rzeczywiste odwołania do obiektów i zapisywać poziom pośredni. Ale te nie wyglądałyby tak ładnie, gdy zostały narysowane w tej odpowiedzi.
To tylko jeden przykład implementacji. Z pewnością są inni. To miało pokazać, że im bliżej pozwalasz tym podtypom kształtowym trzymać się ich pojedynczej odpowiedzialności, tym lepiej działa cały system. Prawdopodobnie istnieją szybsze i wymagające mniej pamięci rozwiązania, ale jeśli zmuszą mnie do rozpowszechnienia wiedzy na temat podtypów wokół, nie chciałbym ich używać nawet przy wzroście wydajności. Nie użyłbym ich, chyba że wyraźnie ich potrzebuję.
Podwójna wysyłka
Do tej pory całkowicie ignorowałem podwójną wysyłkę . Zrobiłem to, ponieważ mogłem. Tak długo, jak kolidująca logika nie dba o to, które dwa typy kolidowały, nie jest to potrzebne. Jeśli go nie potrzebujesz, nie używaj go. Jeśli uważasz, że możesz go potrzebować, odłóż radzenie sobie z nim tak długo, jak możesz. Takie podejście nazywa się YAGNI .
Jeśli zdecydujesz, że naprawdę potrzebujesz różnych rodzajów kolizji, zapytaj siebie, czy n podtypów kształtu naprawdę potrzebuje n 2 rodzajów kolizji. Do tej pory bardzo ciężko pracowałem, aby ułatwić dodanie innego podtypu kształtu. Nie chcę zepsuć go implementacją podwójnej wysyłki, która zmusza okręgi do rozpoznania istnienia kwadratów.
Ile jest rodzajów kolizji? Trochę spekulacji (niebezpieczna rzecz) wymyśla zderzenia sprężyste (sprężyste), nieelastyczne (lepkie), energetyczne (eksplodujące) i niszczące (szkodliwe). Może być ich więcej, ale jeśli jest to mniej niż n 2, nie przepuszczajmy naszych kolizji.
Oznacza to, że gdy moja torpeda trafi w coś, co przyjmuje obrażenia, nie musi WIEDZIEĆ, że uderzyła w statek kosmiczny. Musi tylko powiedzieć: „Ha ha! Odebrałeś 5 punktów obrażeń”.
Rzeczy, które zadają obrażenia, wysyłają komunikaty o uszkodzeniach do rzeczy, które przyjmują komunikaty o uszkodzeniach. W ten sposób możesz dodawać nowe kształty bez informowania innych o nowym kształcie. Ostatecznie rozprzestrzeniasz się tylko wokół nowych rodzajów kolizji.
Statek kosmiczny może odesłać z powrotem na torpę „Ha ha! Odebrałeś 100 punktów obrażeń”. a także „Utknąłeś teraz w moim kadłubie”. A torp może odesłać: „Cóż, skończyłem, więc zapomnij o mnie”.
W żadnym momencie nie wie dokładnie, co to jest. Po prostu wiedzą, jak ze sobą rozmawiać przez interfejs kolizyjny.
Teraz, na pewno, podwójna wysyłka pozwala kontrolować rzeczy bardziej ściśle niż to, ale czy naprawdę tego chcesz ?
Jeśli tak, pomyśl przynajmniej o podwójnej wysyłce poprzez abstrakcje, jakie rodzaje kolizji przyjmuje kształt, a nie o rzeczywistej implementacji kształtu. Ponadto zachowanie podczas kolizji jest czymś, co można wstrzyknąć jako zależność i przekazać do tej zależności.
Występ
Wydajność jest zawsze krytyczna. Ale to nie znaczy, że zawsze jest to problem. Testuj wydajność. Nie tylko spekuluj. Poświęcenie wszystkiego innego w imię wydajności zwykle nie prowadzi do wykonania kodu.