Precisamos implementar uma máquina de estado simples em C .
Uma declaração de switch padrão é a melhor maneira de fazer isso?
Temos um estado atual (estado) e um gatilho para a transição.
switch(state)
{
case STATE_1:
state = DoState1(transition);
break;
case STATE_2:
state = DoState2(transition);
break;
}
...
DoState2(int transition)
{
// Do State Work
...
if(transition == FROM_STATE_2) {
// New state when doing STATE 2 -> STATE 2
}
if(transition == FROM_STATE_1) {
// New State when moving STATE 1 -> STATE 2
}
return new_state;
}
Existe uma maneira melhor para máquinas de estado simples
EDIT: Para C ++, acho que a biblioteca Boost Statechart pode ser o caminho a percorrer. No entanto, isso não ajuda com C. Vamos nos concentrar no caso de uso de C.
c
design-patterns
finite-automata
Benoit
fonte
fonte
Respostas:
Eu prefiro usar uma abordagem baseada em tabela para a maioria das máquinas de estado:
Isso pode, é claro, ser estendido para suportar várias máquinas de estado, etc. As ações de transição também podem ser acomodadas:
A abordagem baseada em tabela é mais fácil de manter e estender e mais simples de mapear para diagramas de estado.
fonte
Você deve ter visto minha resposta a outra pergunta C, onde mencionei FSM! É assim que eu faço:
Com as seguintes macros definidas
Isso pode ser modificado para se adequar ao caso específico. Por exemplo, você pode ter um arquivo
FSMFILE
que deseja direcionar ao seu FSM, então você pode incorporar a ação de ler o próximo caractere na própria macro:agora você tem dois tipos de transições: uma vai para um estado e lê um novo caractere, a outra vai para um estado sem consumir nenhuma entrada.
Você também pode automatizar o manuseio de EOF com algo como:
A vantagem dessa abordagem é que você pode traduzir diretamente um diagrama de estado desenhado em código funcional e, inversamente, pode facilmente desenhar um diagrama de estado a partir do código.
Em outras técnicas de implementação de FSM, a estrutura das transições está enterrada em estruturas de controle (while, if, switch ...) e controlada por valor de variáveis (tipicamente um
state
variável) e pode ser uma tarefa complexa relacionar o belo diagrama a um código complicado.Aprendi essa técnica com um artigo publicado na grande revista "Computer Language" que, infelizmente, não é mais publicado.
fonte
Eu também usei a abordagem da mesa. No entanto, existe uma sobrecarga. Por que armazenar uma segunda lista de ponteiros? Uma função em C sem o () é um ponteiro const. Então você pode fazer:
Claro, dependendo do seu fator de medo (ou seja, segurança versus velocidade), você pode querer verificar se há dicas válidas. Para máquinas de estado maiores do que três ou mais estados, a abordagem acima deve ser menos instruções do que uma chave equivalente ou abordagem de tabela. Você pode até macro-izar como:
Além disso, sinto, a partir do exemplo do OP, que há uma simplificação que deve ser feita ao pensar sobre / projetar uma máquina de estado. Não creio que o estado de transição deva ser usado para lógica. Cada função de estado deve ser capaz de desempenhar sua função dada sem conhecimento explícito do (s) estado (s) passado (s). Basicamente, você projeta como fazer a transição do estado em que se encontra para outro estado.
Finalmente, não comece o projeto de uma máquina de estado com base em limites "funcionais", use subfunções para isso. Em vez disso, divida os estados com base em quando você terá que esperar algo acontecer antes de continuar. Isso ajudará a minimizar o número de vezes que você precisa executar a máquina de estado antes de obter um resultado. Isso pode ser importante ao escrever funções de E / S ou manipuladores de interrupção.
Além disso, alguns prós e contras da declaração switch clássica:
Prós:
Contras:
Observe os dois atributos que são prós e contras. Acho que a mudança permite a oportunidade de muito compartilhamento entre os estados, e a interdependência entre os estados pode se tornar incontrolável. No entanto, para um pequeno número de estados, pode ser o mais legível e sustentável.
fonte
Para uma máquina de estado simples, basta usar uma instrução switch e um tipo de enum para seu estado. Faça suas transições dentro da instrução switch com base em sua entrada. Em um programa real, você obviamente mudaria o "if (input)" para verificar seus pontos de transição. Espero que isto ajude.
fonte
No UML Distilled de Martin Fowler , ele afirma (sem trocadilhos) no Capítulo 10 Diagramas de Máquina de Estado (grifo meu):
Vamos usar um exemplo simplificado dos estados da tela de um telefone celular:
Switch aninhado
Fowler deu um exemplo de código C #, mas eu o adaptei ao meu exemplo.
Padrão de estado
Aqui está uma implementação do meu exemplo com o padrão GoF State:
Tabelas de estado
Inspirando-se em Fowler, aqui está uma tabela para meu exemplo:
Comparação
O switch aninhado mantém toda a lógica em um local, mas o código pode ser difícil de ler quando há muitos estados e transições. É possivelmente mais seguro e fácil de validar do que as outras abordagens (sem polimorfismo ou interpretação).
A implementação do padrão State potencialmente espalha a lógica por várias classes separadas, o que pode dificultar sua compreensão como um todo. Por outro lado, as turmas pequenas são fáceis de entender separadamente. O design é particularmente frágil se você alterar o comportamento adicionando ou removendo transições, pois são métodos na hierarquia e pode haver muitas alterações no código. Se você seguir o princípio de design de interfaces pequenas, verá que esse padrão realmente não funciona tão bem. No entanto, se a máquina de estado for estável, essas mudanças não serão necessárias.
A abordagem das tabelas de estado requer a escrita de algum tipo de interpretador para o conteúdo (isso pode ser mais fácil se você tiver reflexos na linguagem que está usando), o que pode ser muito trabalhoso desde o início. Como Fowler aponta, se sua tabela for separada de seu código, você pode modificar o comportamento de seu software sem recompilar. Isso tem algumas implicações de segurança, no entanto; o software está se comportando com base no conteúdo de um arquivo externo.
Editar (não realmente para a linguagem C)
Também existe uma abordagem de interface fluente (também conhecida como Linguagem Específica de Domínio Interna), que provavelmente é facilitada por linguagens que possuem funções de primeira classe . A biblioteca Stateless existe e esse blog mostra um exemplo simples com código. Uma implementação Java (pré Java8) é discutida. Também vi um exemplo de Python no GitHub .
fonte
há também a grade lógica que é mais sustentável conforme a máquina de estado fica maior
fonte
Para casos simples, você pode mudar o método de estilo. O que descobri que funciona bem no passado também é lidar com as transições:
Não sei nada sobre a biblioteca boost, mas esse tipo de abordagem é muito simples, não requer nenhuma dependência externa e é fácil de implementar.
fonte
switch () é uma maneira poderosa e padrão de implementar máquinas de estado em C, mas pode diminuir a capacidade de manutenção se você tiver um grande número de estados. Outro método comum é usar ponteiros de função para armazenar o próximo estado. Este exemplo simples implementa um flip-flop set / reset:
fonte
Eu encontrei uma implementação em C realmente inteligente de Moore FSM no curso edx.org Embedded Systems - Shape the World UTAustinX - UT.6.02x, capítulo 10, de Jonathan Valvano e Ramesh Yerraballi ....
fonte
Você pode querer dar uma olhada no software gerador do libero FSM. A partir de uma linguagem de descrição de estado e / ou um editor de diagrama de estado (Windows), você pode gerar código para C, C ++, java e muitos outros ... além de documentação e diagramas legais. Fonte e binários do iMatix
fonte
Este artigo é bom para o padrão de estado (embora seja C ++, não especificamente C).
Se você puder colocar as mãos no livro " Head First Design Patterns ", a explicação e o exemplo são muito claros.
fonte
Um dos meus padrões favoritos é o padrão de design de estado. Responda ou se comporte de maneira diferente para o mesmo conjunto de entradas fornecido.
Um dos problemas com o uso de instruções switch / case para máquinas de estado é que à medida que você cria mais estados, o switch / cases torna-se mais difícil / difícil de ler / manter, promove código espaguete desorganizado e cada vez mais difícil de alterar sem quebrar algo. Acho que usar padrões de projeto me ajuda a organizar melhor meus dados, o que é todo o ponto de abstração. Em vez de projetar seu código de estado em torno de qual estado você veio, em vez disso, estruture seu código para que ele registre o estado quando você entrar em um novo estado. Dessa forma, você obtém efetivamente um registro de seu estado anterior. Gosto da resposta de @JoshPetit e levei sua solução um passo adiante, tirada diretamente do livro GoF:
stateCtxt.h:
stateCtxt.c:
statehandlers.h:
statehandlers.c:
Para a maioria das máquinas de estado, esp. Máquinas de estados finitos, cada estado saberá qual deve ser seu próximo estado e os critérios para fazer a transição para seu próximo estado. Para designs de estado flexível, esse pode não ser o caso, daí a opção de expor a API para estados de transição. Se você deseja mais abstração, cada manipulador de estado pode ser separado em seu próprio arquivo, que são equivalentes aos manipuladores de estado concreto no livro GoF. Se o seu design for simples com apenas alguns estados, stateCtxt.c e statehandlers.c podem ser combinados em um único arquivo para simplificar.
fonte
Na minha experiência, o uso da instrução 'switch' é a maneira padrão de lidar com vários estados possíveis. Embora eu esteja surpreso que você esteja passando um valor de transição para o processamento por estado. Achei que o objetivo principal de uma máquina de estado era que cada estado executava uma única ação. Então, a próxima ação / entrada determina para qual novo estado fazer a transição. Portanto, eu esperava que cada função de processamento de estado executasse imediatamente tudo o que for corrigido para entrar no estado e, depois, decidisse se a transição para outro estado é necessária.
fonte
Existe um livro intitulado Practical Statecharts in C / C ++ . No entanto, é maneira demasiado pesado para o que precisamos.
fonte
Para compiladores que suportam
__COUNTER__
, você pode usá-los para mashines de estado simples (mas grandes).A vantagem de usar em
__COUNTER__
vez de números codificados é que você pode adicionar estados no meio de outros estados, sem renumerar tudo sempre. Se o compilador não suporta__COUNTER__
, de forma limitada é possível usar com precaução__LINE__
fonte
__COUNTER__
elimina a necessidade de renumerar, porque o pré-compilador faz a numeração durante a compilação.Você pode usar a estrutura de máquina de estado UML minimalista em c. https://github.com/kiishor/UML-State-Machine-in-C
Ele suporta máquinas de estado finitas e hierárquicas. Possui apenas 3 APIs, 2 estruturas e 1 enumeração.
A máquina de estado é representada por
state_machine_t
estrutura. É uma estrutura abstrata que pode ser herdada para criar uma máquina de estado.O estado é representado por um ponteiro para a
state_t
estrutura no framework.Se a estrutura estiver configurada para máquina de estado finito, então
state_t
contém,A estrutura fornece uma API
dispatch_event
para despachar o evento para a máquina de estado e duas APIs para a passagem de estado.Para obter mais detalhes sobre como implementar a máquina de estado hierárquico, consulte o repositório GitHub.
exemplos de código
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in -C / blob / master / demo / simple_state_machine_enhanced / readme.md
fonte
Em C ++, considere o padrão State .
fonte
Sua pergunta é semelhante a "existe um padrão típico de implementação de banco de dados"? A resposta depende do que você deseja alcançar? Se você deseja implementar uma máquina de estado determinística maior, você pode usar um modelo e um gerador de máquina de estado. Os exemplos podem ser vistos em www.StateSoft.org - SM Gallery. Janusz Dobrowolski
fonte
Boost tem a biblioteca de gráficos de estados. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
Eu não posso falar sobre como usá-lo, no entanto. Não usei sozinho (ainda)
fonte