BobDalgleish ya ha notado que este patrón (anti-) se llama "tramp data ".
En mi experiencia, la causa más común de un exceso de datos de tramp es tener un montón de variables de estado vinculadas que realmente deberían encapsularse en un objeto o una estructura de datos. A veces, incluso puede ser necesario anidar un montón de objetos para organizar correctamente los datos.
Para un ejemplo simple, considera un juego que tiene un personaje de jugador personalizable, con propiedades como playerName
, playerEyeColor
y así sucesivamente. Por supuesto, el jugador también tiene una posición física en el mapa del juego y varias otras propiedades como, por ejemplo, el nivel de salud actual y máximo, y así sucesivamente.
En una primera versión de un juego de este tipo, podría ser una opción perfectamente razonable convertir todas estas propiedades en variables globales. Después de todo, solo hay un jugador, y casi todo en el juego involucra al jugador. Por lo tanto, su estado global puede contener variables como:
playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100
Pero en algún momento, es posible que necesites cambiar este diseño, quizás porque quieres agregar un modo multijugador al juego. Como primer intento, puede intentar hacer que todas esas variables sean locales y pasarlas a las funciones que las necesitan. Sin embargo, puede encontrar que una acción en particular en su juego podría involucrar una función de cadena de llamadas como, por ejemplo:
mainGameLoop()
-> processInputEvent()
-> doPlayerAction()
-> movePlayer()
-> checkCollision()
-> interactWithNPC()
-> interactWithShopkeeper()
... y la función interactWithShopkeeper()
hace que el comerciante se dirija al jugador por su nombre, por lo que ahora de repente necesitas pasar playerName
como datos de trampa a través de todas esas funciones. Y, por supuesto, si el comerciante piensa que los jugadores de ojos azules son ingenuos, y cobrarán precios más altos por ellos, entonces deberá pasar playerEyeColor
a través de toda la cadena de funciones, y así sucesivamente.
La solución adecuada , en este caso, es, por supuesto, definir un objeto de jugador que encapsule el nombre, el color de ojos, la posición, la salud y cualquier otra propiedad del personaje del jugador. De esa manera, solo necesitas pasar ese único objeto a todas las funciones que de alguna manera involucran al jugador.
Además, varias de las funciones anteriores podrían convertirse naturalmente en métodos de ese objeto de jugador, lo que les daría acceso a las propiedades del jugador automáticamente. En cierto modo, esto es solo azúcar sintáctica, ya que llamar a un método en un objeto efectivamente pasa la instancia del objeto como un parámetro oculto al método de todos modos, pero hace que el código se vea más claro y más natural si se usa correctamente.
Por supuesto, un juego típico tendría un estado mucho más "global" que solo el jugador; por ejemplo, es casi seguro que tenga algún tipo de mapa en el que se desarrolla el juego, y una lista de personajes que no son jugadores que se mueven en el mapa, y quizás elementos que se encuentran en él, y así sucesivamente. También podrías pasar a todos aquellos como objetos vagabundos, pero eso volvería a saturar los argumentos de tu método.
En cambio, la solución es que los objetos almacenen referencias a cualquier otro objeto con el que tengan relaciones permanentes o temporales. Así, por ejemplo, el objeto jugador (y probablemente también cualquier objeto NPC) probablemente debería almacenar una referencia al objeto "mundo de juego", que tendría una referencia al nivel / mapa actual, de modo que un método como player.moveTo(x, y)
sí tiene No es necesario que se le dé explícitamente el mapa como parámetro.
De manera similar, si nuestro personaje de jugador tuviera, digamos, un perro mascota que los siguiera, naturalmente agruparíamos todas las variables de estado que describen al perro en un solo objeto, y le daríamos al objeto del jugador una referencia al perro (para que el jugador puede, digamos, llamar al perro por su nombre) y viceversa (para que el perro sepa dónde está el jugador). Y, por supuesto, probablemente querríamos que el jugador y el perro objeten las dos subclases de un objeto "actor" más genérico, de modo que podamos reutilizar el mismo código para, digamos, mover ambos por el mapa.
Sal. Aunque he usado un juego como ejemplo, hay otros tipos de programas en los que también surgen problemas de este tipo. Sin embargo, en mi experiencia, el problema subyacente tiende a ser siempre el mismo: tienes un montón de variables separadas (ya sean locales o globales) que realmente quieren agruparse en uno o más objetos interconectados. Si los "datos de tramp" que se introducen en sus funciones consisten en configuraciones de opciones "globales" o consultas de base de datos en caché o vectores de estado en una simulación numérica, la solución es invariablemente identificar el contexto natural al que pertenecen los datos. y conviértalo en un objeto (o lo que sea el equivalente más cercano en su idioma elegido).