Resolvendo o problema da web if, é possível criar um mecanismo de regras em que cada regra específica seja codificada independentemente. Um refinamento adicional para isso seria criar uma linguagem específica de domínio (DSL) para criar as regras, no entanto, apenas uma DSL desloca o problema de uma base de código (principal) para outra (DSL). Sem estrutura, o DSL não se sairá melhor do que a linguagem nativa (Java, C # etc.), portanto, voltaremos a ela depois de encontrarmos uma abordagem estrutural aprimorada.
A questão fundamental é que você está tendo um problema de modelagem. Sempre que você encontrar situações combinatórias como essa, é um sinal claro de que a abstração do seu modelo que descreve a situação é muito grossa. Você provavelmente está combinando elementos que devem pertencer a modelos diferentes em uma única entidade.
Se você continuar quebrando seu modelo, acabará por dissolver completamente esse efeito combinatório. No entanto, ao seguir esse caminho, é fácil se perder em seu design, criando uma confusão ainda maior, o perfeccionismo aqui não é necessariamente seu amigo.
Máquinas de estado finito e mecanismos de regras são apenas um exemplo de como esse problema pode ser quebrado e tornado mais gerenciável. A idéia principal aqui é que uma boa maneira de se livrar de um problema combinatório como esse é criar um design e repeti-lo ad-nauseam em níveis aninhados de abstração até que seu sistema funcione satisfatoriamente. Semelhante a como os fractais são usados para criar padrões complexos. As regras permanecem as mesmas, não importa se você olha para o seu sistema com um microscópio ou com uma visão panorâmica.
Exemplo de aplicação disso ao seu domínio.
Você está tentando modelar como as vacas estão se movendo por um terreno. Embora sua pergunta não tenha detalhes, eu acho que sua grande quantidade de ifs inclui fragmentos de decisão como, if cow.isStanding then cow.canRun = true
mas você fica atolado ao adicionar detalhes do terreno, por exemplo. Portanto, para cada ação que você deseja executar, verifique todos os aspectos em que você pode pensar e repita essas verificações para a próxima ação possível.
Primeiro, precisamos de nosso design repetitivo, que neste caso será um FSM para modelar os estados variáveis da simulação. Portanto, a primeira coisa que eu faria é implementar um FSM de referência, definindo uma interface de estado , uma interface de transição e, talvez, um contexto de transiçãoque pode conter informações compartilhadas a serem disponibilizadas para os outros dois. Uma implementação básica do FSM mudará de uma transição para outra, independentemente do contexto; é aí que entra um mecanismo de regras. O mecanismo de regras encapsula de maneira limpa as condições que devem ser atendidas para que a transição ocorra. Um mecanismo de regras aqui pode ser tão simples quanto uma lista de regras, cada uma tendo uma função de avaliação retornando um valor booleano. Para verificar se uma transição deve ocorrer, itere a lista de regras e, se alguma delas for avaliada como falsa, a transição não ocorrerá. A transição em si conterá o código comportamental para modificar o estado atual do FSM (e outras tarefas possíveis).
Agora, se eu começar a implementar a simulação como um único FSM grande no nível GOD, terminarei com MUITOS estados possíveis, transições etc. A bagunça if-else parece que está consertada, mas na verdade é apenas espalhada: cada IF é agora uma regra que executa um teste com relação a informações específicas do contexto (que neste momento contêm praticamente tudo) e cada corpo IF está em algum lugar no código de transição.
Digite a decomposição dos fractais: o primeiro passo seria criar um FSM para cada vaca, onde os estados são os estados internos da própria vaca (em pé, correndo, andando, pastando etc.) e as transições entre elas seriam afetadas pelo ambiente. É possível que o gráfico não esteja completo, por exemplo, o pastoreio só é acessível a partir do estado permanente, qualquer outra transição é desaprovada, porque simplesmente está ausente no modelo. Aqui você efetivamente separa os dados em dois modelos diferentes, a vaca e o terreno. Cada um com seu próprio conjunto de propriedades. Essa análise permitirá que você simplifique o design geral do seu motor. Agora, em vez de ter um único mecanismo de regras que decide tudo, você tem vários mecanismos de regras mais simples (um para cada transição) que decidem detalhes muito específicos.
Como estou reutilizando o mesmo código para o FSM, isso é basicamente uma configuração do FSM. Lembra quando mencionamos as DSLs anteriormente? É aqui que o DSL pode fazer muito bem se você tiver muitas regras e transições para escrever.
Indo mais fundo
Agora, DEUS não precisa mais lidar com toda a complexidade no gerenciamento dos estados internos da vaca, mas podemos avançar ainda mais. Ainda há muita complexidade envolvida no gerenciamento do terreno, por exemplo. É aqui que você decide onde a repartição é suficiente. Se, por exemplo, no seu DEUS você acaba gerenciando a dinâmica do terreno (grama alta, lama, lama seca, grama curta, etc.), podemos repetir o mesmo padrão. Não há nada que impeça a incorporação dessa lógica no próprio terreno, extraindo todos os estados do terreno (grama longa, grama curta, lamacenta, seca etc.) em um novo FSM de terreno com transições entre os estados e talvez regras simples. Por exemplo, para chegar ao estado lamacento, o mecanismo de regras deve verificar o contexto para encontrar líquidos, caso contrário, não é possível. Agora DEUS ficou mais simples ainda.
Você pode concluir o sistema do FSM, tornando-os autônomos e dando a cada um deles um encadeamento. Este último passo não é necessário, mas permite alterar dinamicamente a interação do sistema, ajustando a forma como você delega sua tomada de decisão (iniciando um FSM especializado ou apenas retornando um estado predeterminado).
Lembre-se de como mencionamos que as transições também podem fazer "outras tarefas possíveis"? Vamos explorar isso adicionando a possibilidade de diferentes modelos (FSM) se comunicarem. Você pode definir um conjunto de eventos e permitir que cada FSM registre o ouvinte desses eventos. Assim, se, por exemplo, uma vaca entra em um terreno hexadecimal, o hex pode registrar ouvintes para alterações de transição. Aqui fica um pouco complicado, porque cada FSM é implementado em um nível muito alto, sem qualquer conhecimento do domínio específico que abriga. No entanto, você pode conseguir isso fazendo com que a vaca publique uma lista de eventos e a célula pode se registrar se vir eventos aos quais pode reagir. Uma boa hierarquia da família de eventos aqui é um bom investimento.
Você pode avançar ainda mais modelando os níveis de nutrientes e o ciclo de crescimento da grama, com ... você adivinhou ... um FSM de grama incorporado ao modelo do terreno.
Se você empurrar a idéia longe o suficiente, DEUS terá muito pouco a fazer, pois todos os aspectos são praticamente autogerenciados, liberando tempo para gastar em coisas mais piedosas.
Recapitular
Como mencionado acima, o FSM aqui não é a solução, apenas um meio para ilustrar que a solução para esse problema não é encontrada no código, por exemplo, mas como você modela seu problema. É provável que outras soluções sejam possíveis e muito melhores que minha proposta de FSM. No entanto, a abordagem "fractais" continua sendo uma boa maneira de lidar com essa dificuldade. Se feito corretamente, você pode alocar dinamicamente níveis mais profundos onde importa, enquanto fornece modelos mais simples onde menos importa. Você pode enfileirar alterações e aplicá-las quando os recursos ficarem mais disponíveis. Em uma sequência de ações, pode não ser tão importante calcular a transferência de nutrientes da vaca para a grama. No entanto, você pode registrar essas transições e aplicar as alterações posteriormente ou apenas aproximar-se com um palpite, substituindo os mecanismos de regras ou talvez substituindo a implementação do FSM por uma versão ingênua mais simples para os elementos que não estão no campo direto de interesse (aquela vaca no outro extremo do campo) para permitir que interações mais detalhadas obtenham o foco e uma parcela maior de recursos. Tudo isso sem nunca revisitar o sistema como um todo; Como cada peça é bem isolada, fica mais fácil criar uma substituição imediata limitando ou estendendo a profundidade do seu modelo. Ao usar um design padrão, você pode aproveitar isso e maximizar os investimentos feitos em ferramentas ad-hoc, como uma DSL, para definir regras ou um vocabulário padrão para eventos, começando novamente em um nível muito alto e adicionando aprimoramentos, conforme necessário. Como cada peça é bem isolada, fica mais fácil criar uma substituição imediata limitando ou estendendo a profundidade do seu modelo. Ao usar um design padrão, você pode aproveitar isso e maximizar os investimentos feitos em ferramentas ad-hoc, como uma DSL, para definir regras ou um vocabulário padrão para eventos, começando novamente em um nível muito alto e adicionando aprimoramentos, conforme necessário. Como cada peça é bem isolada, fica mais fácil criar uma substituição imediata limitando ou estendendo a profundidade do seu modelo. Ao usar um design padrão, você pode aproveitar isso e maximizar os investimentos feitos em ferramentas ad-hoc, como uma DSL, para definir regras ou um vocabulário padrão para eventos, começando novamente em um nível muito alto e adicionando aprimoramentos, conforme necessário.
Eu forneceria um exemplo de código, mas isso é tudo o que posso fazer agora.