BobDalgleish zauważył już, że ten (anty-) wzorzec nazywany jest „ danymi trampowymi ”.
Z mojego doświadczenia wynika, że najczęstszą przyczyną nadmiernej ilości danych trampowych jest wiązka zmiennych stanu połączonych, które powinny być naprawdę zamknięte w obiekcie lub strukturze danych. Czasami może być nawet konieczne zagnieżdżenie wielu obiektów w celu prawidłowej organizacji danych.
Dla prostego przykładu rozważmy grę, która posiada konfigurowalny player charakter, o właściwościach podobnych playerName
, playerEyeColor
i tak dalej. Oczywiście gracz ma również fizyczną pozycję na mapie gry i różne inne właściwości, takie jak, powiedzmy, aktualny i maksymalny poziom zdrowia i tak dalej.
Podczas pierwszej iteracji takiej gry rozsądnym wyborem może być przekształcenie wszystkich tych właściwości w zmienne globalne - w końcu jest tylko jeden gracz i prawie wszystko w grze w jakiś sposób dotyczy gracza. Więc twój stan globalny może zawierać zmienne takie jak:
playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100
Ale w pewnym momencie może się okazać, że musisz zmienić ten projekt, być może dlatego, że chcesz dodać do gry tryb wieloosobowy. Jako pierwszą próbę możesz spróbować ustawić wszystkie te zmienne lokalnie i przekazać je do funkcji, które ich potrzebują. Może się jednak okazać, że określone działanie w grze może obejmować łańcuch wywołań funkcji, na przykład:
mainGameLoop()
-> processInputEvent()
-> doPlayerAction()
-> movePlayer()
-> checkCollision()
-> interactWithNPC()
-> interactWithShopkeeper()
... a interactWithShopkeeper()
funkcja ma adres sprzedawcy do gracza po imieniu, więc teraz musisz nagle przekazać playerName
dane trampowe przez wszystkie te funkcje. I oczywiście, jeśli sprzedawca myśli, że niebieskoocy gracze są naiwni, i będzie pobierać za nich wyższe ceny, wówczas musisz przejść playerEyeColor
przez cały łańcuch funkcji i tak dalej.
Właściwe rozwiązanie, w tym przypadku, to oczywiście do zdefiniowania obiektu odtwarzacza, która zamyka nazwa, kolor oczu, stanowisko, zdrowia i innych właściwości postać gracza. W ten sposób wystarczy przekazać ten pojedynczy obiekt wszystkim funkcjom, które w jakiś sposób dotyczą odtwarzacza.
Ponadto niektóre z powyższych funkcji można naturalnie przekształcić w metody tego obiektu gracza, co automatycznie zapewniłoby im dostęp do właściwości gracza. W pewnym sensie jest to po prostu cukier składniowy, ponieważ wywołanie metody na obiekcie skutecznie przekazuje instancję obiektu jako parametr ukryty do metody, ale sprawia, że kod wygląda na wyraźniejszy i bardziej naturalny, jeśli jest właściwie stosowany.
Oczywiście, typowa gra miałaby znacznie bardziej „globalny” stan niż tylko gracz; na przykład, prawie na pewno miałbyś jakąś mapę, na której gra się toczy, oraz listę postaci niebędących graczami poruszającymi się po mapie, a może umieszczone na niej przedmioty i tak dalej. Możesz przekazać je wszystkie jako obiekty trampowe, ale to znowu zaśmieci twoje argumenty metody.
Zamiast tego rozwiązaniem jest, aby obiekty przechowywały odniesienia do wszelkich innych obiektów, z którymi mają stałe lub tymczasowe relacje. Na przykład obiekt gracza (i prawdopodobnie także wszelkie obiekty NPC) prawdopodobnie powinien przechowywać odniesienie do obiektu „świata gry”, który miałby odniesienie do bieżącego poziomu / mapy, aby metoda player.moveTo(x, y)
taka nie musiała otrzymać jawnie mapę jako parametr.
Podobnie, gdyby nasza postać gracza miała, powiedzmy, psa, który za nimi podążał, naturalnie pogrupowalibyśmy wszystkie zmienne stanu opisujące psa w jeden obiekt i nadaliśmy obiektowi gracza odniesienie do psa (aby gracz mógł powiedzmy, nazwij psa po imieniu) i odwrotnie (aby pies wiedział, gdzie jest gracz). I oczywiście chcielibyśmy, aby gracz i pies sprzeciwiali się obu podklasom bardziej ogólnego obiektu „aktora”, abyśmy mogli ponownie użyć tego samego kodu do, powiedzmy, poruszania się po mapie.
Ps. Mimo że użyłem gry jako przykładu, istnieją inne rodzaje programów, w których pojawiają się takie problemy. Z mojego doświadczenia wynika jednak, że podstawowy problem jest zawsze taki sam: masz kilka oddzielnych zmiennych (lokalnych lub globalnych), które naprawdę chcą być połączone w jeden lub więcej powiązanych ze sobą obiektów. Niezależnie od tego, czy „dane trampowe” wtrącające się w twoje funkcje obejmują ustawienia „globalne”, buforowane zapytania do bazy danych lub wektory stanu w symulacji numerycznej, rozwiązaniem jest niezmiennie określenie naturalnego kontekstu , do którego należą dane, i przekształcenie go w obiekt (lub jakikolwiek najbliższy odpowiednik w wybranym języku).