Atualizar:
Mais uma vez obrigado pelos exemplos, eles foram muito úteis e, com o seguinte, não pretendo tirar nada deles.
Não são os exemplos atualmente dados, até onde eu os entendo e máquinas de estado, apenas metade do que geralmente entendemos por uma máquina de estado?
No sentido de que os exemplos mudam de estado, mas isso é representado apenas pela alteração do valor de uma variável (e permitindo diferentes alterações de valor em diferentes estados), enquanto geralmente uma máquina de estado também deve mudar seu comportamento, e comportamento não (somente) em o senso de permitir que valores diferentes mudem para uma variável dependendo do estado, mas no sentido de permitir que diferentes métodos sejam executados para diferentes estados.
Ou tenho um conceito errado de máquinas de estado e seu uso comum?
Cumprimentos
Pergunta original:
Eu encontrei essa discussão sobre máquinas de estado e blocos de iteradores em c # e ferramentas para criar máquinas de estado e o que não era para C #, então achei muitas coisas abstratas, mas como noob tudo isso é um pouco confuso.
Portanto, seria ótimo se alguém pudesse fornecer um exemplo de código-fonte em C # que realize uma máquina de estado simples com talvez 3,4 estados, apenas para entender o essencial.
fonte
Respostas:
Vamos começar com este diagrama de estado simples:
Nós temos:
Você pode convertê-lo em C # de várias maneiras, como executar uma instrução switch no estado e comando atuais ou procurar transições em uma tabela de transição. Para esta máquina de estado simples, prefiro uma tabela de transição, que é muito fácil de representar usando
Dictionary
:Por uma questão de preferência pessoal, eu gosto de projetar minhas máquinas de estado com uma
GetNext
função para retornar o próximo estado deterministicamente e umaMoveNext
função para alterar a máquina de estado.fonte
GetHashCode()
uso de números primos.StateTransition
classe é usada como chave no dicionário e a igualdade de chaves é importante. Duas instâncias distintas deStateTransition
devem ser consideradas iguais desde que representem a mesma transição (por exemplo,CurrentState
eCommand
sejam iguais). Para implementar a igualdade, você deve substituirEquals
tambémGetHashCode
. Em particular, o dicionário usará o código hash e dois objetos iguais devem retornar o mesmo código hash. Você também obtém um bom desempenho se não houver muitos objetos diferentes iguais que compartilhem o mesmo código de hash, e é por isso queGetHashCode
é implementado como mostrado.Você pode querer usar uma das máquinas de estado finito de código aberto existentes. Por exemplo, bbv.Common.StateMachine encontrado em http://code.google.com/p/bbvcommon/wiki/StateMachine . Possui uma sintaxe fluente muito intuitiva e muitos recursos, como ações de entrada / saída, ações de transição, guardas, implementação hierárquica, passiva (executada no encadeamento do chamador) e implementação ativa (encadeamento próprio no qual o fsm é executado, eventos são adicionados a uma fila).
Tomando o exemplo de Juliets, a definição para a máquina de estados fica muito fácil:
Atualização : o local do projeto foi movido para: https://github.com/appccelerate/statemachine
fonte
Aqui está um exemplo de uma máquina de estado finito muito clássica, modelando um dispositivo eletrônico muito simplificado (como uma TV)
fonte
private void DoNothing() {return;}
e substitui todas as instâncias de null porthis.DoNothing
. Tem o agradável efeito colateral de retornar o estado atual.States
paraUnpowered, Standby, On
. Meu raciocínio é que, se alguém me perguntasse em que estado minha televisão está, eu diria "Desligado" e não "Iniciar". Eu também mudeiStandbyWhenOn
eStandbyWhenOff
paraTurnOn
eTurnOff
. Isso faz com que o código seja lido de maneira mais intuitiva, mas me pergunto se existem convenções ou outros fatores que tornam minha terminologia menos apropriada.Algumas promoções sem vergonha aqui, mas há um tempo atrás eu criei uma biblioteca chamada YieldMachine que permite que uma máquina de estado de complexidade limitada seja descrita de uma maneira muito limpa e simples. Por exemplo, considere uma lâmpada:
Observe que esta máquina de estado possui 2 gatilhos e 3 estados. No código YieldMachine, escrevemos um método único para todo comportamento relacionado ao estado, no qual cometemos a atrocidade horrível de usar
goto
para cada estado. Um gatilho se torna uma propriedade ou campo do tipoAction
, decorado com um atributo chamadoTrigger
. Eu comentei o código do primeiro estado e suas transições abaixo; os próximos estados seguem o mesmo padrão.Curto e legal, hein!
Essa máquina de estado é controlada simplesmente enviando gatilhos para ela:
Apenas para esclarecer, adicionamos alguns comentários ao primeiro estado para ajudar você a entender como usar isso.
Isso funciona porque o compilador C # realmente criou uma máquina de estado internamente para cada método que usa
yield return
. Essa construção geralmente é usada para criar preguiçosamente sequências de dados, mas, neste caso, não estamos realmente interessados na sequência retornada (que é nula de qualquer maneira), mas no comportamento do estado criado sob o capô.A
StateMachine
classe base faz alguma reflexão sobre a construção para atribuir código a cada[Trigger]
ação, que define oTrigger
membro e move a máquina de estado para frente.Mas você realmente não precisa entender os internos para poder usá-lo.
fonte
goto
método entre.goto
pular entre métodos? Não vejo como isso funcionaria. Não,goto
é problemático porque resulta em programação procedural (isso por si só complica coisas boas como teste de unidade), promove a repetição de código (percebeu comoInvalidTrigger
precisa ser inserido para cada estado?) E, finalmente, torna o programa mais difícil de seguir. Compare isso com (a maioria) outras soluções neste segmento e você verá que este é o único onde todo o FSM acontece em um único método. Isso geralmente é suficiente para suscitar uma preocupação.goto
muito bem.goto
saltar entre funções, mas não suporta funções? :) Você está certo, a observação "mais difícil de seguir" é mais umagoto
questão geral , na verdade não é um problema tão grande neste caso.Você pode codificar um bloco iterador que permite executar um bloco de código de forma orquestrada. Como o bloco de código é dividido realmente não precisa corresponder a nada, é exatamente como você deseja codificá-lo. Por exemplo:
Nesse caso, quando você chama CountToTen, nada ainda é executado. O que você obtém é efetivamente um gerador de máquina de estado, para o qual você pode criar uma nova instância da máquina de estado. Você faz isso chamando GetEnumerator (). O IEnumerator resultante é efetivamente uma máquina de estado que você pode dirigir chamando MoveNext (...).
Portanto, neste exemplo, na primeira vez em que você chamar MoveNext (...), você verá "1" gravado no console e, na próxima vez em que chamar MoveNext (...), verá 2, 3, 4 e depois 5, 6, 7 e 8 e 9, 10. Como você pode ver, é um mecanismo útil para orquestrar como as coisas devem ocorrer.
fonte
Estou postando outra resposta aqui, pois são máquinas de estado de uma perspectiva diferente; muito visual.
Minha resposta original é o código imperativo clássico. Eu acho que é bastante visual como o código ocorre por causa da matriz que simplifica a visualização da máquina de estado. A desvantagem é que você deve escrever tudo isso. A resposta de Remos alivia o esforço de escrever o código da placa da caldeira, mas é muito menos visual. Existe a terceira alternativa; realmente desenhando a máquina de estado.
Se você estiver usando o .NET e puder direcionar a versão 4 do tempo de execução, terá a opção de usar as atividades da máquina de estado do fluxo de trabalho . Essencialmente, eles permitem desenhar a máquina de estados (como no diagrama de Juliet ) e fazer com que o tempo de execução do WF a execute para você.
Consulte o artigo MSDN Criando máquinas de estado com o Windows Workflow Foundation para obter mais detalhes e este site do CodePlex para a versão mais recente.
Essa é a opção que eu sempre preferiria quando visasse o .NET, porque é fácil de ver, alterar e explicar para não programadores; as imagens valem mais que mil palavras, como se costuma dizer!
fonte
É útil lembrar que as máquinas de estado são uma abstração e você não precisa de ferramentas específicas para criar uma, mas as ferramentas podem ser úteis.
Você pode, por exemplo, realizar uma máquina de estados com funções:
Essa máquina caçava gaivotas e tentava atingi-las com balões de água. Se errar, ele tentará disparar um até atingir (o que poderia acontecer com algumas expectativas realistas;)); caso contrário, ele se alegrará no console. Ele continua a caçar até ficar sem gaivotas para assediar.
Cada função corresponde a cada estado; os estados inicial e final (ou aceito ) não são mostrados. Provavelmente existem mais estados do que modelados pelas funções. Por exemplo, depois de disparar o balão, a máquina está realmente em outro estado do que estava antes, mas decidi que essa distinção era impraticável.
Uma maneira comum é usar classes para representar estados e conectá-las de maneiras diferentes.
fonte
Encontrei este ótimo tutorial on-line e me ajudou a entender melhor as máquinas de estado finito.
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
O tutorial é independente da linguagem, portanto pode ser facilmente adaptado às suas necessidades de C #.
Além disso, o exemplo usado (uma formiga à procura de comida) é fácil de entender.
Do tutorial:
fonte
Hoje eu estou profundamente envolvido no Design de Estado. Fiz e testei o ThreadState, que é igual a (+/-) a Threading em C #, conforme descrito na figura em Threading em C #
Você pode facilmente adicionar novos estados, configurar movimentos de um estado para outro é muito fácil, pois está incluído na implementação do estado
Implementação e uso em: Implementa .NET ThreadState por State Design Pattern
fonte
Ainda não tentei implementar um FSM em C #, mas tudo isso soa (ou parece) muito complicado pela maneira como lidei com FSM no passado em linguagens de baixo nível como C ou ASM.
Eu acredito que o método que eu sempre conheci se chama algo como "Loop Iterativo". Nele, você basicamente possui um loop 'while' que sai periodicamente com base em eventos (interrupções) e depois retorna ao loop principal novamente.
Dentro dos manipuladores de interrupção, você passaria um CurrentState e retornaria um NextState, que substitui a variável CurrentState no loop principal. Você faz isso ad infinitum até o programa fechar (ou o microcontrolador redefinir).
O que estou vendo outras respostas parece muito complicado em comparação com a forma como um FSM é, na minha opinião, destinado a ser implementado; sua beleza reside em sua simplicidade e o FSM pode ser muito complicado com muitos estados e transições, mas eles permitem que processos complicados sejam facilmente decompostos e digeridos.
Sei que minha resposta não deve incluir outra pergunta, mas sou forçado a perguntar: por que essas outras soluções propostas parecem ser tão complicadas?
Eles parecem assemelhar-se a um pequeno prego com uma marreta gigante.
fonte
Que tal StatePattern. Isso atende às suas necessidades?
Eu acho que seu contexto está relacionado, mas vale a pena tentar com certeza.
http://en.wikipedia.org/wiki/State_pattern
Isso permite que seus estados decidam para onde ir e não a classe "objeto".
Bruno
fonte
Na minha opinião, uma máquina de estados não serve apenas para alterar estados, mas também (muito importante) para manipular gatilhos / eventos dentro de um estado específico. Se você deseja entender melhor o padrão de projeto da máquina de estado, uma boa descrição pode ser encontrada no livro Padrões de design do primeiro cabeçote, página 320 .
Não é apenas sobre os estados nas variáveis, mas também sobre o manuseio de gatilhos nos diferentes estados. Ótimo capítulo (e não, não há nenhuma taxa para mim ao mencionar isso :-), que contém apenas uma explicação fácil de entender.
fonte
Eu acabei de contribuir com isso:
https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC
Aqui está um dos exemplos de demonstração do envio direto e indireto de comandos, com estados como IObserver (de sinal), respondendo assim a uma fonte de sinal, IObservable (de sinal):
Nota: este exemplo é bastante artificial e serve principalmente para demonstrar uma série de recursos ortogonais. Raramente deve haver uma necessidade real de implementar o próprio domínio de valor de estado por uma classe completa, usando o CRTP (consulte: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) como este.
Aqui está um caso de uso de implementação certamente mais simples e provavelmente muito mais comum (usando um tipo de enum simples como o domínio de valor de estados), para a mesma máquina de estado e com o mesmo caso de teste:
https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs
«HTH
fonte
StateChange
resolvido? Através da reflexão? Isso é mesmo necessário?private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
Eu criei essa máquina de estado genérica a partir do código de Juliet. Está funcionando demais para mim.
Estes são os benefícios:
TState
eTCommand
,TransitionResult<TState>
para ter mais controle sobre os resultados de saída dos[Try]GetNext()
métodosStateTransition
única atravésAddTransition(TState, TCommand, TState)
tornando-se mais fácil trabalhar com eleCódigo:
Este é o tipo de retorno do método TryGetNext:
Como usar:
É assim que você pode criar a
OnlineDiscountStateMachine
partir da classe genérica:Defina uma enumeração
OnlineDiscountState
para seus estados e uma enumeraçãoOnlineDiscountCommand
para seus comandos.Definir uma classe
OnlineDiscountStateMachine
derivada da classe genérica usando essas duas enumeraçõesDerive o construtor
base(OnlineDiscountState.InitialState)
para que o estado inicial seja definido comoOnlineDiscountState.InitialState
Use
AddTransition
quantas vezes for necessáriouse a máquina de estados derivada
fonte
Eu acho que a máquina de estado proposta por Juliet tem um erro: o método GetHashCode pode retornar o mesmo código de hash para duas transições diferentes, por exemplo:
Para evitar esse erro, o método deve ser assim:
Alex
fonte
int
valores possíveis ). É por isso queHashCode
é sempre implementado junto comEquals
. Se os códigos de hash forem iguais, os objetos serão verificados quanto à exatidão exata usando oEquals
métodoFiniteStateMachine é uma Máquina de Estado Simples, escrita em C # Link
Vantagens de usar minha biblioteca FiniteStateMachine:
Baixar DLL Download
Exemplo no LINQPad:
fonte
Eu recomendaria state.cs . Eu pessoalmente usei state.js (a versão JavaScript) e estou muito feliz com isso. Essa versão C # funciona de maneira semelhante.
Você instancia estados:
Você instancia algumas transições:
Você define ações em estados e transições:
E é isso (praticamente). Consulte o site para obter mais informações.
fonte
Existem 2 pacotes populares de máquinas de estado no NuGet.
Appccelerate.StateMachine (13.6K downloads + 3.82K da versão herdada (bbv.Common.StateMachine))
StateMachineToolkit (1,56K downloads)
A Appccelerate lib possui boa documentação , mas não suporta o .NET 4, por isso escolhi o StateMachineToolkit para o meu projeto.
fonte
Outra alternativa neste repositório https://github.com/lingkodsoft/StateBliss usou sintaxe fluente, suporta gatilhos.
fonte
Você pode usar minha solução, esta é a maneira mais conveniente. Também é grátis.
Crie uma máquina de estados em três etapas:
1. Crie um esquema no editor de nós🔗 e carregue-o no seu projeto usando a biblioteca📚
2. Descreva a lógica do seu aplicativo em eventos⚡
3. Execute a máquina de estado🚘
Ligações:
Editor de nó: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor
Biblioteca: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary
fonte