Lab3 Leandro Lima

aluno: Leandro Lima
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 12/10/08 (3)

Introdução

Nesse 3° Laboratório de CES-22, criou-se uma biblioteca de máquina de estados finita e determinística movida a eventos. Através dela, pode-se criar máquinas que implementem todas as suas funcionalidades em diversas situações. Visando adequar as particularidades da máquina aos conteúdos da disciplina vistos em aula, foram utilizados os padrões Observer e Command, além de classes anônimas.

Desenvolvimento

Para implementar a máquina de estados referida, colocamos todas as classes em um único pacote. Contudo, em programas com nível de complexidade maior é interessante que haja uma divisão das classes considerando seus principais aspectos, escopos de atuação, nível de acoplamento e semântica.

Diagramas de Classes

Aqui podemos ver como ficou o Diagrama de Classes do programa:

Classes.JPG

Classe StateMachine

Essa é a principal classe de nosso programa, funcionando como uma interface pública. Como variáveis de instância, além do estado atual, foram utilizadas ArrayLists para armazenar os estados, os eventos e os observadores. Isso foi feito com o intuito de utilizar uma abordagem mais simplificada. Alguns métodos adicionais foram implementados para prover recursos, notadamente aos tipos de retorno desejados. Os estados, juntamente com os observadores e os eventos, foram armazenados em ArrayLists. Essa abordagem tem uma desvantagem: a de ter que armazenar em cada célula da List um terno (event, source e destination). Desse modo, percebe-se que um evento pode estar associado a mais de uma célula. Isso foi resolvido pelo método findEventByName e com seu método auxiliar doesStateExist. Alternativamente, poderíamos contar com uma lista auxiliar para armazenar todas as transições.

package stateMachine;
 
import java.util.ArrayList;
 
public class StateMachine {
 
    private State currentState;
    private ArrayList<State> states = new ArrayList<State>();
    private ArrayList<Event> events = new ArrayList<Event>();
    private ArrayList<Observer> observers = new ArrayList<Observer>();
 
    //@Override
    public void appendTransitionToEvent(String event, String fromState,
            String toState) {
 
        Event eventoNovo = new Event(event, findStateByName(fromState), findStateByName(toState));        
        this.events.add(eventoNovo);
 
    }
 
    //@Override
    public void appendTransitionToEvent(String event, String[] fromState,
            String toState) {
 
        for(int i = 0; i < fromState.length; i++){
            Event eventoNovo = new Event(event, findStateByName(fromState[i]), findStateByName(toState));        
            this.events.add(eventoNovo);            
        }
 
    }
 
    //@Override
    public void createEvent(String name, String[] from, String to,
            EventCommand onSuccess) {
        for(int i = 0; i < from.length; i++){
            Event eventoNovo = new Event(name,findStateByName(from[i]),
                    findStateByName(to), onSuccess);
            this.events.add(eventoNovo);            
        }
    }
 
    //@Override
    public void createEvent(String name, String[] from, String to) {
        for(int i = 0; i < from.length; i++){
            Event eventoNovo = new Event(name,findStateByName(from[i]),findStateByName(to));
            this.events.add(eventoNovo);            
        }
    }
 
    //@Override
    public void createEvent(String name, String from, String to,
            EventCommand onSuccess) {
        Event eventoNovo = new Event(name,findStateByName(from),
                findStateByName(to), onSuccess);
        this.events.add(eventoNovo);
    }
 
    //@Override
    public void createEvent(String name, String from, String to) {
        Event eventoNovo = new Event(name,findStateByName(from),findStateByName(to));
        this.events.add(eventoNovo);
 
    }
 
    //@Override
    public void createState(String name, StateCommand onEnter) {
        State state = new State(name, onEnter);
        states.add(state);
    }
 
    //@Override
    public void createState(String name) {
        State state = new State(name, null);
        states.add(state);
    }
 
    //@Override
    public boolean fireEvent(String name) throws UndefinedTransitionException {
        Event e = findEventByName(name);
 
        if (e.getEventGuard() != null)
            if (e.getEventGuard().shouldPerform() == false)
                return false;
 
        this.currentState = e.getDestinationState();
        e.executeCommand();
        notifyObservers();
        this.currentState.executeCommand();
        return true;
    }
 
    private Event findEventByName(String name) throws UndefinedEventException, UndefinedStateException, UndefinedTransitionException {
        Boolean foundEvent = false;
 
        for(int i = 0; i < events.size(); i++)
            if (events.get(i).getName().equals(name)){
 
                if (!doesStateExists(events.get(i).getDestinationState()))
                    //|| !doesStateExists(events.get(i).getSourceState())
                    throw new UndefinedStateException();
 
                if (events.get(i).getSourceState().equals(this.currentState))
                    return events.get(i);
 
                foundEvent = true;
            }
 
        if (foundEvent)
            throw new UndefinedTransitionException();
 
        throw new UndefinedEventException();
    }
 
    . . .
 
    //@Override
    public void setGuardToEvent(String event, EventGuard g) {
 
        for(int i = 0; i < events.size(); i++)
            if (events.get(i).getName().equals(event))
                events.get(i).setEventGuard(g);
    }
 
    //@Override
    public void setInitialState(String name) {
        this.currentState = findStateByName(name);
    }
 
    private State findStateByName(String name) { //throws UndefinedStateException {
 
        for(int i = 0; i < states.size(); i++)
            if (states.get(i).getName().equals(name))
                return states.get(i);
 
        return null;
        //throw new UndefinedStateException();
    }
 
    private Boolean doesStateExists(State state) {
 
        for(int i = 0; i < states.size(); i++)
            if (states.get(i).equals(state))
                return true;
 
        return false;
    }    
 
}

Classe State e Observer

Classe State criada para concretizar os atributos de estado e viabilizar a execução de StateCommand. Os observadores são registrados pela própria máquina de estados para serem atualizados de seu estado atual e com isso realizar as modificações necessárias (método update). O conceito de classes anônimas foi explorado aqui e foi muito útil, pois um determinado método (StateCommand - método de chegada no estado) já é executado no momento de sua declaração (um objeto é passado como parâmetro).

package stateMachine;
 
public class State {
    private String name;
    private StateCommand command;
 
    public State(String name, StateCommand command) {
        this.name = name;
        this.command = command;
    }
 
    public String getName() {
        return name;
    }
 
    public void executeCommand() {
        if (this.command == null) return;
        command.execute();
    }
}

Classes de Exceções (UndefinedEventException, UndefinedStateException e UndefinedTransitionException)

Essas classes foram criadas como subclasses de Exception e visam lançar uma exceção se algo de errado ocorreu no programa (as de Evento e Estado são avaliadas em tempo de execução). Vale observar que o método findEventByName foi criado para lançar 2 exceções (Event e Transition), contudo foi escolhida uma implementação para lançar as 3 exceções, cada qual em uma situação diferente. A exceção de Estado, por exemplo, é lançada se o estado de destino não existe, porém uma outra alternativa seria também verificar se a origem não existe (esse comando está comentado no código de StateMachine).

package stateMachine;
 
public class UndefinedEventException extends RuntimeException {
 
}
package stateMachine;
 
public class UndefinedStateException extends RuntimeException {
 
}
package stateMachine;
 
public class UndefinedTransitionException extends Exception {
 
}

Classe de Teste (StateMachineTest)

O teste ShouldReturnErrorIfEventDoesntExist durante uma das implementações intermediárias do programa estava verificando no createEvent se os estados existiam e lançando Exceção de Transição, mas os testes só estavam dando erro em Exceções de Evento (não existia o SourceState, mas existia o evento). Daí, a escolha por lançar mais de uma exceção. O comando catch usado no teste independe da ordem que é usado (o comando captura de qualquer índice da pilha de exceções geradas). Em testes adicionais, foi visto que o resultado se mantém o mesmo ao trocar a ordem dos catchs.

O nome de UndefinedEventError foi alterado para UndefinedEventException para manter a consistência dos testes.

Teste.JPG

Classes de Evento (EventGuard, EventCommand e Event)

As exceções lançadas em findEventByName (comentadas na seção de exceções) são justificadas pois uma das etapas do teste deixava o evento ser criados mesmo que não existisse a transição para um estado válido. A exceção seria lançada somente ao se disparar esse evento.
A classe Event também foi criada pois serviria de veículo do EventGuard com o EventCommand, ocupando células na ArrayList de Eventos. Cada evento está associado a um evento de origem e um de destino específico. Como visto, as ações e condições de guarda foram definidas, com o uso de classes anônimas, quando se chamam os métodos que criam os eventos e estados.

package stateMachine;
 
public class Event {
    private String name;
    private State sourceState, destinationState;
    private EventCommand command;
    private EventGuard eventGuard;
 
    public Event(String name, State sourceState, State destinationState) {
        super();
        this.name = name;
        this.sourceState = sourceState;
        this.destinationState = destinationState;
        this.command = null;
        this.eventGuard = null;
    }
 
    public Event(String name, State sourceState, State destinationState,
            EventCommand command) {
        super();
        this.name = name;
        this.sourceState = sourceState;
        this.destinationState = destinationState;
        this.command = command;
    }
 
    public void executeCommand(){
        if (command == null) return;
        this.command.execute(this.sourceState.getName());
    }
 
    . . .
 
}

Exemplo de Aplicação (Dado em Sala)

Nesse exemplo podemos enxergar o Diagrama de Classes:

ExemploSala.JPG

Para enxergamos a aplicação da máquina de estados, iremos percorrer os estados numa certa ordem e avaliando as respostas do programa. O comportamento da turma vai sendo observado pelo professor e, dependendo de qual seja, ele muda seu estado também.

Inicialmente, o professor encontra-se "motivado" e a turma "dormindo".

ExemploSala-1.JPG

Agora a turma dispara "usar_note":

ExemploSala-2.JPG

Note que a correcao da prova vai mudando tambem.

ExemploSala-3.JPG

Agora a turma tenta motivar o professor:

ExemploSala-4.JPG

Agora ele volta a ficar "motivado":

ExemploSala-5.JPG

A turma dispara "dormir":

ExemploSala-6.JPG

O professor volta a ficar "desmotivado".

ExemploSala-7.JPG

Contudo, a turma dispara "usar_note":

ExemploSala-8.JPG

Agora o professor está "irado". Ele nunca mais será o mesmo. Repare que a correção da prova também vai mudando.

ExemploSala-9.JPG

Se a turma quiser prestar atenção…

ExemploSala-10.JPG

…ele já estará "vacinado".

ExemploSala-11.JPG

Agora o professor está recebendo milhões de dólares.

ExemploSala-12.JPG

Mesmo que a turma tente desmotivá-lo, isso não muda seu estado para "irado" (o mesmo funciona de "motivado" para "desmotivado").

ExemploSala-13.JPG ExemploSala-14.JPG

Uma mostra da evolução da resposta ao longo do tempo pode ser observado no Terminal exibido a seguir:

Terminal.JPG

Exemplo de Aplicação (Novo)

O novo exemplo de aplicação consiste no seguinte fluxo de estados:

ExemploNovo.JPG

As alterações ente os estados são dadas pelo eventos: Trabalhar, Vadiar e Mega-Sena. No exemplo anterior foi feita uma execução passo-a-passo, contudo nesse exemplo as funcionalidades serão implementadas diretamente numa classe Main.

Classe Main

O estado inicial varia aleatoriamente entre "aluno e "vagabundo".

package ExemploNovo;
 
public class Main {
 
    public static void main(String[] args) {
        Individuo pessoa = new Individuo();
 
        System.out.println("Olá! Meu estado inicial é: " + pessoa.getState());
 
        if (pessoa.getState() == "aluno"){
 
            System.out.println("\n(Vadiando)\n");
            pessoa.fire("vadiar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Vadiando)\n");
            pessoa.fire("vadiar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
        }
 
        else {
 
            System.out.println("\n(Vadiando)\n");
            pessoa.fire("vadiar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Apostando)\n");
            pessoa.fire("mega-sena");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Trabalhando)\n");
            pessoa.fire("trabalhar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
 
            System.out.println("\n(Vadiando)\n");
            pessoa.fire("vadiar");
            System.out.println("Meu estado atual é: " + pessoa.getState());
            System.out.println(pessoa.salario());
        }
    }
}

Classe Individuo

package ExemploNovo;
 
public class Individuo extends WithSM
{
    public Individuo() {
        super();
        setUpSM();
    }
 
    public String salario() {
        if(getState() == "aluno" || getState() == "vagabundo")
            return "Eita vida dificil... to ganhando nada.";
        else if(getState() == "esperancoso")
            return "To juntando as micharias pra fazer uma fezinha.";
        else if(getState() == "trainee")
            return "Opa! Já dá pra levar a namorada pro cinema!";
        else if (getState() == "funcionario")
            return "Agora, finalmente compro meu carrinho.";
        else if (getState() == "gerente")
            return "Férias = Viagens!!";
        else
            return "HAHAHA!! Vivo num mundo sem limites!!";
    }
 
    private void setUpSM() {
 
        //criacao dos estados
        sm.createState("aluno");
        sm.createState("trainee");
        sm.createState("funcionario");
        sm.createState("gerente");
        sm.createState("vagabundo");
        sm.createState("esperancoso");
        sm.createState("rico", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Estou rico!!");
            }
        } );
 
        //definido o estado inicial
        int a = (int) (Math.random()*2);
        if (a == 0)
            sm.setInitialState("vagabundo");
        else
            sm.setInitialState("aluno");
 
        //evento trabalhar
        sm.createEvent("trabalhar", "rico", "rico", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Como serei ainda mais rico?? HAHA!");
            }
        });
 
        sm.appendTransitionToEvent("trabalhar", "gerente", "rico");
        sm.appendTransitionToEvent("trabalhar", "aluno", "trainee");
        sm.appendTransitionToEvent("trabalhar", "trainee", "funcionario");
        sm.appendTransitionToEvent("trabalhar", "funcionario", "gerente");
        sm.appendTransitionToEvent("trabalhar", "vagabundo", "esperancoso");
 
        //evento vadiar
        sm.createEvent("vadiar", "vagabundo", "vagabundo", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("É o fundo do poço!");
            }
        });
 
        sm.createEvent("vadiar", "rico", "rico", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Nunca deixarei de ser rico!");
            }
        });
 
        sm.appendTransitionToEvent("vadiar", "aluno", "aluno");
        sm.appendTransitionToEvent("vadiar", "esperancoso", "vagabundo");
        sm.appendTransitionToEvent("vadiar", "trainee", "aluno");
        sm.appendTransitionToEvent("vadiar", "funcionario", "trainee");
        sm.appendTransitionToEvent("vadiar", "gerente", "funcionario");
 
        //evento mega-sena
        sm.createEvent("mega-sena", "esperancoso", "rico", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Ganhei na loteria!");
            }
        });
 
    }
}

Classe WithSM

package ExemploNovo;
 
import stateMachine.*;
 
public class WithSM
{
    protected StateMachine sm;
    public WithSM() {
         sm = new StateMachine();
    }
 
    public void fire(String eventName){
        try{
          sm.fireEvent(eventName);
        } catch(UndefinedTransitionException e) {
            System.out.println("ERRO: transição não definida: " + e.getMessage());
        }
    }
 
    public String getState(){
        return sm.getCurrentStateName();
    }
}

Terminal do BlueJ

TerminalExNovo1.JPGTerminalExNovo2.JPG

Conclusão

Como descrito ao longo do relatório, foram utilizadas implementações intermediárias nesse programa que atendiam às especificações, porém não passava nos testes. Isso comprova o alto nível de complexidade da prática e nos ensina que, ao executar projetos desse tipo, temos que sempre seguir um desenvolvimento orientado a testes (as criações dos métodos são realizadas gradualmente, conforme se vai passando nos testes), como foi realizado. Se não fosse isso, a implementação seria bem mais difícil, pois grandes blocos de código teriam que ser avaliados de uma só vez.

Durante o desenvolvimento do programa, procurou-se criar as classes objetivando um baixo nivel de acoplamento, porém, por optar por estruturas de dados mais simples para armazenar eventos e estados, isso foi algo conseguido com certa dificuldade.

Uma melhoria sugerida para o projeto seria a de incluir no próprio teste de unidade métodos que verificassem o funcionamento da interface Observer e os métodos de appendTransitionToEvent, que foram explorados apenas no exemplo. Em uma versão intermediária, o programa estava executando o StateCommand antes de mudar seu estado atual. Isso não foi coberto pelos testes e poderia ser explorado.

A criação de uma biblioteca de máquina de estados é extremamente útil, pois pode ser utilizada por outros programas que necessitem de um grafo de fluxo de estados e interações de eventos, como jogos (inclusive o do projeto do exame).

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License