¿Cuál es la mejor manera de resolver una explosión combinatoria de interacciones?
Una de las cosas en las que estoy trabajando ahora tiene algunas similitudes con un juego. Con fines ilustrativos, voy a explicar mi problema utilizando un ejemplo extraído de un juego ficticio e hipotético.
Vamos a llamarloDeathBlaster 4: The Deathening. En DB4, tiene un número deShip
objetos que se encuentran periódicamente y al azarPhenomena
mientras viajan. Un dadoPhenomenon
puede tener cero, uno o másEffects
en unShip
que lo encuentra. Por ejemplo, podríamos tener cuatro tipos deShips
y tres tipos dePhenomena
.
Phenomena ========================================== Ships GravityWell BlackHole NebulaField ------------ ------------------------------------------ RedShip +20% speed -50% power -50% shield BlueShip no effect invulnerable death Effects of Various GreenShip -20% speed death +50% shield Phenomena on Ships YellowShip death +50% power no effect
Adicionalmente,Effects
pueden interactuar entre sí. Por ejemplo, unaGreenShip
que está tanto en unGravityWell
y unNebulaField
puede derivar algún tipo de sinergia entre el @ generaSpeedEffect
yShieldEffect
. En tales casos, el efecto sinérgico es en sí mismo unEffect
- por ejemplo, puede haber unaPowerLevelSynergyEffect
que resulta de esta interacción. No hay otra información que no sea el conjunto deEffects
actuando en unShip
es necesario para resolver cuál debería ser el resultado final.
Puede comenzar a ver un problema emergente aquí. Como un primer acercamiento ingenuo, o cadaShip
tendrá que saber cómo manejar cadaPhenomenon
o cadaPhenomenon
tendrá que saber sobre cadaShip
. Obviamente, esto es inaceptable, por lo que nos gustaría trasladar estas responsabilidades a otra parte. Claramente hay al menos una clase externa aquí, tal vez unaMediator
oVisitor
de algún tipo
Pero, ¿cuál es la mejor manera de hacer eso? La solución ideal probablemente tendrá estas propiedades:
Es tan fácil agregar una nuevaShip
como es agregar una nuevaPhenomenon
. Las interacciones que no producen ningún efecto son las predeterminadas y no requieren código adicional para representarlas. Convención sobre configuración Comprende cómoEffects
interactúa entre sí y es capaz de gestionar estas interacciones para decidir cuál será el resultado final.Ya he decidido cuál será mi enfoque, creo, pero me interesa saber cuál es el mejor consenso de diseño. ¿Por dónde empezarías? ¿Qué caminos explorarías?
Actualización de seguimiento: Gracias por sus respuestas a todos. Esto es lo que terminé haciendo. Mi observación principal fue que el número de diferentesEffects
parece ser pequeño en relación con el número de posiblesPhenomena
× Ships
interacciones. Es decir, aunque hay muchas posibles combinaciones de interacciones, el número de tipos de resultados de esas interacciones es un número menor.
uede ver que, por ejemplo, aunque hay 12 combinaciones de interacción en la tabla, solo hay cinco tipos de efectos: modificaciones a la velocidad, modificaciones al poder, modificaciones al escudo, invulnerabilidad, muerte.
Introduje una tercera clase, laInteractionResolver
, para determinar el resultado de las interacciones. Contiene un diccionario que asignaShip-Phenomenon
pares aEffects
(que son básicamente un delegado que realiza el efecto y algunos metadatos). CadaShip
recibe unaEffectStack
correspondiente a laEffects
está experimentando cuando se completa el resultado de calcular la interacción.
Ships
luego use laEffectStack
para determinar el resultado real de laEffects
en ellos, agregando modificadores a sus atributos y propiedades existentes.
Me gusta esto porque:
os barcos nunca necesitan saber sobre los fenómenoLa complejidad de la relación Barco-Fenómenos se abstrae en InteractionResolver. Los detalles de cómo resolver efectos múltiples y posiblemente complejos se abstraen conInteractionResolver
. Los barcos solo tienen que aplicar los efectos según sea necesario. Esto permite refactorizaciones útiles adicionales. Por ejemplo, lacamin en el que un barco procesa los efectos se pueden diferenciar haciendo unEffectProcessorStrategy
. El valor predeterminado podría ser procesar todos los efectos, pero, digamos, unBossShip
podría ignorar efectos menores al tener una @ diferenEffectProcessorStrategy
.