Al abordar el problema de si web , puede crear un motor de reglas donde cada regla específica se codifique de forma independiente. Una mejora adicional para esto sería crear un lenguaje específico de dominio (DSL) para crear las reglas, sin embargo, un DSL solo desplaza el problema de una base de código (principal) a otra (DSL). Sin estructura, el DSL no funcionará mejor que el lenguaje nativo (Java, C #, etc.), por lo que volveremos a él después de encontrar un enfoque estructural mejorado.
El problema fundamental es que tiene un problema de modelización. Siempre que se encuentre con situaciones combinatorias como esta, es una señal clara de que la abstracción de su modelo que describe la situación es demasiado burda. Lo más probable es que combine elementos que deberían pertenecer a diferentes modelos en una sola entidad.
Si sigue desglosando su modelo, eventualmente disolverá por completo este efecto combinatorio. Sin embargo, al tomar este camino es fácil perderse en su diseño creando un desastre aún mayor, el perfeccionismo aquí no es necesariamente su amigo.
Las máquinas de estados finitos y los motores de reglas son solo un ejemplo de cómo este problema puede desglosarse y hacerse más manejable. La idea principal aquí es que una buena manera de deshacerse de un problema combinatorio como este es a menudo crear un diseño y repetirlo ad-nauseam en niveles anidados de abstracción hasta que su sistema funcione satisfactoriamente. Similar a cómo se usan los fractales para crear patrones intrincados. Las reglas siguen siendo las mismas sin importar si miras tu sistema con un microscopio o desde una vista panorámica.
Ejemplo de aplicar esto a su dominio.
Estás intentando modelar cómo se mueven las vacas por un terreno. Aunque su pregunta carece de detalles, supongo que su gran cantidad de ifs incluye un fragmento de decisión como, por ejemplo, if cow.isStanding then cow.canRun = true
que se atasca a medida que agrega detalles del terreno. Por lo tanto, para cada acción que desee tomar, debe verificar todos los aspectos que pueda pensar y repetir estas verificaciones para la próxima acción posible.
Primero necesitamos nuestro diseño repetible, que en este caso será un FSM para modelar los estados cambiantes de la simulación. Entonces, lo primero que haría es implementar un FSM de referencia, definir una interfaz de estado , una interfaz de transición y quizás un contexto de transiciónque puede contener información compartida que se pondrá a disposición de los otros dos. Una implementación básica de FSM cambiará de una transición a otra independientemente del contexto, aquí es donde entra en juego un motor de reglas. El motor de reglas encapsula limpiamente las condiciones que deben cumplirse para que la transición tenga lugar. Un motor de reglas aquí puede ser tan simple como una lista de reglas, cada una con una función de evaluación que devuelve un valor booleano. Para verificar si se debe realizar una transición, iteremos la lista de reglas y si alguna de ellas se evalúa como falsa, la transición no tiene lugar. La transición en sí contendrá el código de comportamiento para modificar el estado actual del FSM (y otras tareas posibles).
Ahora, si empiezo a implementar la simulación como un solo FSM grande en el nivel de DIOS, termino con MUCHOS estados posibles, transiciones, etc. Parece que el lío if-else está solucionado, pero en realidad solo se extiende: cada IF es ahora una regla que realiza una prueba contra una información específica del contexto (que en este punto contiene casi todo) y cada cuerpo IF está en algún lugar del código de transición.
Ingrese el desglose de fractales: el primer paso sería crear un FSM para cada vaca donde los estados son los propios estados internos de la vaca (de pie, correr, caminar, pastar, etc.) y las transiciones entre ellas se verían afectadas por el medio ambiente. Es posible que el gráfico no esté completo, por ejemplo, el pastoreo solo es accesible desde el estado de reposo, cualquier otra transición se descarta porque simplemente está ausente del modelo. Aquí separa efectivamente los datos en dos modelos diferentes, la vaca y el terreno. Cada uno con su propio conjunto de propiedades. Este desglose le permitirá simplificar el diseño general del motor. Ahora, en lugar de tener un único motor de reglas que decida todo lo que tiene, tiene motores de reglas múltiples y más simples (uno para cada transición) que deciden detalles muy específicos.
Debido a que estoy reutilizando el mismo código para el FSM, esta es básicamente una configuración del FSM. ¿Recuerdas cuando mencionamos DSL antes? Aquí es donde el DSL puede hacer mucho bien si tiene muchas reglas y transiciones para escribir.
Yendo más profundo
Ahora DIOS ya no tiene que lidiar con toda la complejidad en el manejo de los estados internos de la vaca, pero podemos impulsarlo aún más. Todavía hay mucha complejidad involucrada en la gestión del terreno, por ejemplo. Aquí es donde decides dónde es suficiente el desglose. Si, por ejemplo, en su DIOS termina manejando la dinámica del terreno (pasto largo, barro, barro seco, pasto corto, etc.) podemos repetir el mismo patrón. No hay nada que le impida incorporar tal lógica en el terreno mismo extrayendo todos los estados del terreno (hierba larga, hierba corta, fangosa, seca, etc.) en un nuevo FSM de terreno con transiciones entre los estados y quizás reglas simples. Por ejemplo, para llegar al estado fangoso, el motor de reglas debe verificar el contexto para encontrar líquidos, de lo contrario no es posible. Ahora Dios se volvió más simple aún.
Puede completar el sistema de FSM haciéndolos autónomos y dándoles un hilo a cada uno. Este último paso no es necesario, pero le permite cambiar dinámicamente la interacción del sistema al ajustar cómo delega su toma de decisiones (iniciar un FSM especializado o simplemente devolver un estado predeterminado).
¿Recuerdas cómo mencionamos que las transiciones también podrían hacer "otras tareas posibles"? Exploremos eso agregando la posibilidad de que diferentes modelos (FSM) se comuniquen entre sí. Puede definir un conjunto de eventos y permitir que cada FSM registre la escucha de estos eventos. Por lo tanto, si, por ejemplo, una vaca entra en un hex de terreno, el hex puede registrar oyentes para los cambios de transición. Aquí se vuelve un poco complicado porque cada FSM se implementa a un nivel muy alto sin ningún conocimiento del dominio específico que alberga. Sin embargo, puede lograr esto haciendo que la vaca publique una lista de eventos y la celda puede registrarse si ve eventos a los que puede reaccionar. Una buena jerarquía de eventos familiares aquí es una buena inversión.
Puede profundizar aún más modelando los niveles de nutrientes y el ciclo de crecimiento de la hierba, con ... lo adivinó ... un FSM de hierba incrustado en el modelo del parche de terreno.
Si llevas la idea lo suficientemente lejos, DIOS tiene muy poco que hacer, ya que todos los aspectos son casi autogestionados, liberando tiempo para gastar en cosas más piadosas.
Resumen
Como se indicó anteriormente, el FSM aquí no es la solución, solo un medio para ilustrar que la solución a dicho problema no se encuentra en el código por ejemplo, sino en cómo modela su problema. Lo más probable es que haya otras soluciones posibles y mucho mejores que mi propuesta FSM. Sin embargo, el enfoque de "fractales" sigue siendo una buena forma de manejar esta dificultad. Si se hace correctamente, puede asignar dinámicamente niveles más profundos donde sea importante y, al mismo tiempo, proporcionar modelos más simples donde sea menos importante. Puede poner en cola los cambios y aplicarlos cuando los recursos estén más disponibles. En una secuencia de acción, puede no ser tan importante calcular la transferencia de nutrientes de la vaca al parche de hierba. Sin embargo, puede registrar estas transiciones y aplicar los cambios en un momento posterior o simplemente aproximarse con una suposición educada simplemente reemplazando los motores de reglas o quizás reemplazando la implementación FSM por una versión ingenua más simple para los elementos que no están en el campo directo de interés (esa vaca en el otro extremo del campo) para permitir interacciones más detalladas para obtener el foco y una mayor parte de los recursos. Todo esto sin volver a visitar el sistema en su conjunto; Como cada parte está bien aislada, resulta más fácil crear un reemplazo directo que limite o extienda la profundidad de su modelo. Al usar un diseño estándar, puede aprovechar eso y maximizar las inversiones realizadas en herramientas ad-hoc como un DSL para definir reglas o un vocabulario estándar para eventos, comenzando nuevamente a un nivel muy alto y agregando refinamientos según sea necesario. Como cada parte está bien aislada, se hace más fácil crear un reemplazo directo que limite o extienda la profundidad de su modelo. Al usar un diseño estándar, puede aprovechar eso y maximizar las inversiones realizadas en herramientas ad-hoc como un DSL para definir reglas o un vocabulario estándar para eventos, comenzando nuevamente a un nivel muy alto y agregando refinamientos según sea necesario. Como cada parte está bien aislada, se hace más fácil crear un reemplazo directo que limite o extienda la profundidad de su modelo. Al usar un diseño estándar, puede aprovechar eso y maximizar las inversiones realizadas en herramientas ad-hoc como un DSL para definir reglas o un vocabulario estándar para eventos, comenzando nuevamente a un nivel muy alto y agregando refinamientos según sea necesario.
Proporcionaría un ejemplo de código, pero esto es todo lo que puedo permitirme ahora.