BobDalgleish ha già notato che questo pattern (anti-) è chiamato " dati di prova ".
Secondo la mia esperienza, la causa più comune di eccessivi dati di vagabondo è costituita da una serie di variabili di stato collegate che dovrebbero essere realmente incapsulate in un oggetto o in una struttura di dati. A volte, può anche essere necessario annidare un gruppo di oggetti per organizzare correttamente i dati.
Per un semplice esempio, considera un gioco con un personaggio giocatore personalizzabile, con proprietà come playerName
, playerEyeColor
e così via. Ovviamente, il giocatore ha anche una posizione fisica sulla mappa di gioco e varie altre proprietà come, ad esempio, il livello di salute attuale e massimo e così via.
In una prima iterazione di un gioco del genere, potrebbe essere una scelta perfettamente ragionevole per trasformare tutte queste proprietà in variabili globali - dopo tutto, c'è solo un giocatore, e quasi tutto nel gioco coinvolge in qualche modo il giocatore. Quindi il tuo stato globale potrebbe contenere variabili come:
playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100
Ma a un certo punto potresti scoprire che è necessario modificare questo disegno, forse perché vuoi aggiungere una modalità multiplayer al gioco. Come primo tentativo, potresti provare a rendere tutte quelle variabili locali e passarle a funzioni che ne hanno bisogno. Tuttavia, potresti scoprire che un'azione particolare nel tuo gioco potrebbe coinvolgere una catena di chiamate di funzioni come, ad esempio:
mainGameLoop()
-> processInputEvent()
-> doPlayerAction()
-> movePlayer()
-> checkCollision()
-> interactWithNPC()
-> interactWithShopkeeper()
... e la funzione interactWithShopkeeper()
ha il negoziante indirizza il giocatore per nome, quindi ora devi improvvisamente passare playerName
come dati tramp attraverso tutte quelle funzioni. E, naturalmente, se il negoziante pensa che i giocatori con gli occhi azzurri siano ingenui e addebiterà loro prezzi più alti, allora dovrai passare playerEyeColor
attraverso l'intera catena di funzioni, e così via.
La soluzione corretta , in questo caso, è ovviamente quella di definire un oggetto giocatore che incapsula il nome, il colore degli occhi, la posizione, la salute e qualsiasi altra proprietà del personaggio del giocatore. In questo modo, devi solo passare quell'oggetto singolo a tutte le funzioni che in qualche modo coinvolgono il giocatore.
Inoltre, molte delle funzioni di cui sopra potrebbero essere naturalmente trasformate in metodi di quell'oggetto giocatore, che darebbe loro automaticamente l'accesso alle proprietà del giocatore. In un certo senso, questo è solo zucchero sintattico, dal momento che chiamare un metodo su un oggetto passa in modo efficace l'istanza dell'oggetto come parametro nascosto al metodo in ogni caso, ma rende il codice più chiaro e più naturale se usato correttamente.
Ovviamente, un gioco tipico avrebbe molto più stato "globale" del solo giocatore; ad esempio, quasi sicuramente avrai una sorta di mappa su cui si svolge il gioco e un elenco di personaggi non giocanti che si spostano sulla mappa, e magari oggetti posizionati su di esso, e così via. Potresti passare tutti quelli intorno come oggetti tramp, ma ciò riempirebbe di nuovo gli argomenti del tuo metodo.
Invece, la soluzione è di fare in modo che gli oggetti memorizzino riferimenti a qualsiasi altro oggetto con cui hanno relazioni permanenti o temporanee. Quindi, ad esempio, l'oggetto giocatore (e probabilmente anche qualsiasi oggetto NPC) probabilmente dovrebbe memorizzare un riferimento all'oggetto "mondo di gioco", che avrebbe un riferimento al livello / mappa corrente, in modo che un metodo come player.moveTo(x, y)
lo faccia non è necessario che venga specificata esplicitamente la mappa come parametro.
Allo stesso modo, se il nostro personaggio avesse, per esempio, un cane che li seguiva, raggrupperemmo tutte le variabili di stato che descrivono il cane in un singolo oggetto e daremo al giocatore oggetto di riferimento al cane (in modo che il giocatore può, ad esempio, chiamare il cane per nome) e viceversa (in modo che il cane sappia dove si trova il giocatore). E, naturalmente, probabilmente vorremmo rendere il giocatore e il cane oggetti entrambi sottoclassi di un oggetto "attore" più generico, così da poter riutilizzare lo stesso codice per, diciamo, spostarci entrambi sulla mappa.
Ps. Anche se ho usato un gioco come esempio, ci sono altri tipi di programmi in cui emergono anche questi problemi. Nella mia esperienza, però, il problema sottostante tende ad essere sempre lo stesso: si hanno un mucchio di variabili separate (sia locali che globali) che vogliono davvero essere raggruppate in uno o più oggetti interconnessi. Se i "dati tramp" che invadono le tue funzioni sono costituiti da impostazioni di opzioni "globali" o query di database o vettori di stato nella cache in una simulazione numerica, la soluzione è invariabilmente per identificare il contesto naturale a cui appartengono i dati e trasformalo in un oggetto (o qualunque sia l'equivalente più vicino nella lingua scelta).