Ale czy metoda draw () nie zależy w dużym stopniu od interfejsu użytkownika?
Z pragmatycznego punktu widzenia, część kodu w twoim systemie musi wiedzieć, jak narysować coś takiego, Rectanglejeśli jest to wymóg użytkownika. A to w pewnym momencie sprowadza się do robienia naprawdę niskich poziomów, takich jak rasteryzacja pikseli lub wyświetlanie czegoś w konsoli.
Pytanie z punktu widzenia sprzężenia brzmi: kto / co powinien zależeć od tego rodzaju informacji i do jakiego stopnia szczegółowości (na przykład jak abstrakcyjny)?
Wyodrębnianie możliwości rysowania / renderowania
Ponieważ jeśli kod rysunkowy wyższego poziomu zależy tylko od czegoś bardzo abstrakcyjnego, abstrakcja ta może być w stanie działać (poprzez podstawienie konkretnych implementacji) na wszystkich platformach, na które zamierzasz celować. Jako wymyślony przykład, niektóre bardzo abstrakcyjne IDrawerinterfejsy mogą być zaimplementowane zarówno w interfejsach API konsoli, jak i GUI, aby wykonywać takie czynności, jak kształty wydruku (implementacja konsoli może traktować konsolę jak jakiś „obraz” 80xN ze sztuką ASCII). Oczywiście jest to wymyślony przykład, ponieważ zwykle nie jest to, co chcesz zrobić, to traktować wyjście konsoli jak bufor obrazu / ramki; zazwyczaj większość użytkowników końcowych potrzebuje więcej interakcji tekstowych w konsolach.
Innym zagadnieniem jest to, jak łatwo zaprojektować stabilną abstrakcję? Ponieważ może być łatwe, jeśli wszystkim, na co celujesz, są nowoczesne interfejsy API GUI, aby wyodrębnić podstawowe możliwości rysowania kształtów, takie jak kreślenie linii, prostokątów, ścieżek, tekstu, tego rodzaju rzeczy (po prostu prosta rasteryzacja 2D ograniczonego zestawu prymitywów) , z jednym abstrakcyjnym interfejsem, który można łatwo zaimplementować za pomocą różnych podtypów przy niewielkich kosztach. Jeśli potrafisz skutecznie zaprojektować taką abstrakcję i wdrożyć ją na wszystkich platformach docelowych, to powiedziałbym, że jest to znacznie mniejsze zło, a nawet w ogóle zło, dla kształtu lub kontroli GUI, lub czegokolwiek, aby wiedzieć, jak narysować się za pomocą takiego abstrakcja.
Ale powiedz, że próbujesz pozbyć się krwawych szczegółów, które różnią się między Playstation Portable, iPhone'em, XBox One i potężnym komputerem do gier, podczas gdy twoje potrzeby to wykorzystanie najnowocześniejszych technik renderowania / cieniowania 3D w czasie rzeczywistym . W takim przypadku próba opracowania jednego abstrakcyjnego interfejsu w celu wyodrębnienia szczegółów renderowania, gdy podstawowe możliwości sprzętowe i interfejsy API różnią się tak bardzo, że niemal na pewno doprowadzi do ogromnego czasu projektowania i przeprojektowania, wysokie prawdopodobieństwo powtarzających się zmian w projekcie z nieoczekiwanymi odkrycia, a także rozwiązanie o najniższym wspólnym mianowniku, które nie wykorzystuje pełnej wyjątkowości i mocy podstawowego sprzętu.
Dokonywanie zależności w kierunku stabilnych, „łatwych” projektów
W mojej dziedzinie jestem w tym ostatnim scenariuszu. Celujemy w wiele różnych urządzeń o radykalnie różnych podstawowych możliwościach i interfejsach API, a próba wymyślenia jednej abstrakcji renderowania / rysowania, aby rządzić nimi wszystkimi, jest beznadziejna bez granic (możemy stać się sławni na całym świecie, robiąc to skutecznie, ponieważ byłaby to gra zmieniacz w branży). Więc ostatnią rzeczą, chcę w moim przypadku jest podobnie jak analogiczne Shapelub Modellub Particle Emitter, że nie wie, jak zwrócić się, nawet jeśli to jest wyrażanie że rysunek na najwyższym poziomie i najbardziej abstrakcyjny sposób możliwe ...
... ponieważ te abstrakcje są zbyt trudne do prawidłowego zaprojektowania, a kiedy projekt jest trudny do poprawienia, a wszystko zależy od niego, jest to przepis na najbardziej kosztowne centralne zmiany projektu, które marszczą i niszczą wszystko w zależności od niego. Ostatnią rzeczą, jakiej chcesz, jest to, aby zależności w twoich systemach płynęły w kierunku abstrakcyjnych projektów zbyt trudnych do poprawienia (zbyt trudnych do ustabilizowania bez ingerencji w zmiany).
Trudne zależy od łatwego, a nie łatwe zależy od trudnego
Zamiast tego sprawiamy, że zależności płyną w kierunku rzeczy, które można łatwo zaprojektować. O wiele łatwiej jest zaprojektować abstrakcyjny „Model”, który koncentruje się na przechowywaniu rzeczy takich jak wielokąty i materiały i uzyskać poprawność tego projektu, niż zaprojektować abstrakcyjny „Renderer”, który można skutecznie wdrożyć (poprzez zastępowalne podtypy betonu) do rysowania usługi żądania jednolicie dla sprzętu tak odmiennego jak PSP z komputera.

Dlatego odwracamy zależności od rzeczy, które są trudne do zaprojektowania. Zamiast sprawić, by modele abstrakcyjne wiedzieli, jak przyciągnąć się do projektu mechanizmu renderującego abstrakcję, od którego wszystkie są zależne (i przerwać ich implementacje, jeśli ten projekt się zmieni), zamiast tego mamy mechanizm renderujący abstrakcję, który wie, jak narysować każdy obiekt abstrakcyjny w naszej scenie ( modeli, emiterów cząstek itp.), dzięki czemu możemy następnie zaimplementować podtyp renderujący OpenGL dla komputerów typu PC RendererGl, inny dla systemów PSP RendererPsp, inny dla telefonów komórkowych itp. W takim przypadku zależności płyną w kierunku stabilnych projektów, łatwych do poprawienia, od renderera do różnego rodzaju bytów (modeli, cząstek, tekstur itp.) w naszej scenie, a nie na odwrót.

- Używam „stabilności / niestabilności” w nieco innym sensie niż miary aferentne / eferentne wuja Boba, które w miarę moich możliwości mierzą trudność zmiany. Mówię bardziej o „prawdopodobieństwie konieczności zmiany”, chociaż jego wskaźnik stabilności jest tam przydatny. Kiedy „prawdopodobieństwo zmiany” jest proporcjonalne do „łatwości zmiany” (np. Rzeczy, które najprawdopodobniej będą wymagać zmian, charakteryzują się najwyższą niestabilnością i sprzężeniami aferentnymi z miary wuja Boba), wówczas wszelkie takie prawdopodobne zmiany są tanie i nieinwazyjne , wymagający jedynie zastąpienia implementacji bez dotykania żadnych centralnych projektów.
Jeśli próbujesz wyodrębnić coś na centralnym poziomie bazy kodu i jest to zbyt trudne do zaprojektowania, zamiast uparcie uderzać głową o ściany i ciągle wprowadzać w tym uciążliwe zmiany każdego miesiąca / roku, co wymaga aktualizacji 8000 plików źródłowych, ponieważ jest to rozbijając wszystko, co od tego zależy, moją pierwszą sugestią jest rozważenie odwrócenia zależności. Sprawdź, czy możesz napisać kod w taki sposób, że rzecz, która jest tak trudna do zaprojektowania, zależy od wszystkiego, co jest łatwiejsze do zaprojektowania, bez posiadania rzeczy, które są łatwiejsze do zaprojektowania, w zależności od rzeczy, które są tak trudne do zaprojektowania. Pamiętaj, że mówię o projektach (w szczególności projektach interfejsów), a nie o implementacjach: czasami rzeczy są łatwe do zaprojektowania i trudne do wdrożenia, a czasem rzeczy są trudne do zaprojektowania, ale łatwe do wdrożenia. Zależności płyną w kierunku projektów, więc należy skupić się tylko na tym, jak trudno jest tutaj zaprojektować coś, aby określić kierunek przepływu zależności.
Zasada pojedynczej odpowiedzialności
Dla mnie SRP nie jest tu zwykle tak interesujące (choć w zależności od kontekstu). Mam na myśli balansowanie na linie podczas projektowania rzeczy, które są jasne w celu i łatwe do utrzymania, ale twoje Shapeobiekty mogą musieć ujawnić bardziej szczegółowe informacje, jeśli na przykład nie wiedzą, jak się rysować, i może nie być wiele znaczących rzeczy do zrobić z kształtem w określonym kontekście użytkowania, niż skonstruować go i narysować. Występują kompromisy z prawie wszystkim i nie jest to związane z SRP, które mogą uświadomić, jak wyciągnąć umiejętność stania się koszmarem konserwacyjnym z mojego doświadczenia w pewnych kontekstach.
Ma on znacznie więcej wspólnego ze sprzężeniem i kierunkiem, w którym zależności płyną w twoim systemie. Jeśli próbujesz przenieść abstrakcyjny interfejs renderowania, od którego wszystko zależy (ponieważ używają go do rysowania), do nowego docelowego interfejsu API / sprzętu i zdajesz sobie sprawę, że musisz znacznie zmienić jego projekt, aby działał tam efektywnie, to jest to bardzo kosztowna zmiana, która wymaga zastąpienia implementacji wszystkiego w twoim systemie, który umie się rysować. I to jest najbardziej praktyczny problem konserwacyjny, jaki napotykam, mając świadomość tego, jak się rysować, jeśli przekłada się to na mnóstwo zależności płynących w kierunku abstrakcji, które są zbyt trudne do prawidłowego zaprojektowania z góry.
Duma programisty
Wspominam o tym jednym punkcie, ponieważ z mojego doświadczenia wynika, że często jest to największa przeszkoda w kierowaniu zależności w kierunku rzeczy łatwiejszych do zaprojektowania. Deweloperom bardzo łatwo jest tutaj podchodzić nieco bardziej ambitnie i powiedzieć: „Zaprojektuję abstrakcyjną platformę renderowania, by rządzić nimi wszystkimi, rozwiążę to, co inni programiści spędzają miesiącami na portowaniu, i dostanę to dobrze i będzie działać jak magia na każdej obsługiwanej przez nas platformie i wykorzystywać najnowocześniejsze techniki renderowania na każdej z nich; już sobie to wyobrażałem ”.W takim przypadku opierają się praktycznemu rozwiązaniu, które polega na uniknięciu tego i po prostu odwracają kierunek zależności i tłumaczą to, co może być niezwykle kosztowne i powtarzające się zmiany w projekcie centralnym, na tanie i lokalne powtarzające się zmiany we wdrażaniu. W programistach musi istnieć instynkt „białej flagi”, aby się poddać, gdy coś jest zbyt trudne do zaprojektowania na tak abstrakcyjnym poziomie i ponownie przemyśleć całą swoją strategię, w przeciwnym razie czeka ich wiele cierpienia i bólu. Sugerowałbym przeniesienie takich ambicji i ducha walki na najnowocześniejsze implementacje łatwiejszej do zaprojektowania niż przeniesienie takich ambicji podbijających świat na poziom projektowania interfejsu.