Lab3 Felipe Moraes

aluno: Felipe Corrêa de Moraes
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 09/10/2008 (3)

Introdução

A solicitação da presente prática é a implementação de uma Máquina de Estados Finita em linguagem java. Temos por objetivo a fixação de conceitos aprendidos em aula teórica, como o uso de classes anônimas, o uso dos padrões Observer e Command, o tratamento de excessões, e a modelagem de uma situação-problema, concebida pelo próprio aluno.

Desenvolvimento

A primeira etapa desta prática consistiu na concepção de um modelo para o desenvolvimento desta aplicação. A figura a seguir mostra como o pacote Statemachine foi modelado (com o auxílio do diagrama lógico que metade da turma recebeu):
Statemachine.JPG

As classes UndefinedTransitionException, UndefinedEventException e UndefinedStateException são as três classes responsáveis pelo tratamento das exceções. A classe StateMachine implementa todos os métodos descritos no guia da prática e armazena as listas dos objetos State, Event e Transition. A classe State possui como atributos o nome do estado, e, eventualmente, a ação do dado estado. A classe Event possui como atributos o nome do evento, a (eventual) ação do evento e a (também opcional) condição de guarda. Por fim, a classe Transition tem como atributos o evento ao qual a transição está associada, o nome do estado de onde a transição parte e o nome do estado para onde a transição chega.

Classe StateMachine:

package Statemachine;
 
import java.util.ArrayList;
 
public class StateMachine
{
    // instance variables
    private ArrayList<Observer> observers = new ArrayList<Observer>();
    private ArrayList<State> states = new ArrayList<State>();
    private ArrayList<Event> events = new ArrayList<Event>();
    private ArrayList<Transition> transitions = new ArrayList<Transition>();
    private State currentstate; 
 
    public void registerObserver(Observer observer)
    {
        observers.add(observer);
    }
 
    protected void notifyObservers()
    {
        for (Observer ob : observers) {
            ob.update(this, currentstate.getName());
        }
    }
 
    public void createState(String name, StateCommand onEnter)
    {
        for(State st : states)
        {
            if(st.getName().equals(name))
            {   
                System.out.println("Estado ja criado");
                return;
            }
        }        
        State state = new State(name, onEnter);
        states.add(state);
    }
 
    public void createState(String name)
    {
        for(State st : states)
        {
            if(st.getName().equals(name))
            {   
                System.out.println("Estado ja criado");
                return;
            }
        }
        State state = new State(name);
        states.add(state);        
    }
 
    public void setInitialState(String name)
    {
        for(State st : states)
        {
            if(st.getName().equals(name))
            {   
                currentstate = st;
                return;
            }
        }
        System.out.println("Estado nao existente");
    }
 
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(name))
            {   
                System.out.println("Evento ja criado");
                return;
            }
        }
        Event event = new Event(name,onSuccess);
        events.add(event);
        for(String str : from)
        {
            Transition transition = new Transition(event,str,to);
            transitions.add(transition);
        }    
    }
 
    public void createEvent(String name, String[] from, String to)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(name))
            {   
                System.out.println("Evento ja criado");
                return;
            }
        }
        Event event = new Event(name);
        events.add(event);
        for(String str : from)
        {
            Transition transition = new Transition(event,str,to);
            transitions.add(transition);
        }    
    }
 
    public void createEvent(String name, String from, String to,  EventCommand onSuccess)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(name))
            {   
                System.out.println("Evento ja criado");
                return;
            }
        }
        Event event = new Event(name,onSuccess);
        Transition transition = new Transition(event,from,to);         
        events.add(event);
        transitions.add(transition);
    }
 
    public void createEvent(String name, String from, String to)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(name))
            {   
                System.out.println("Evento ja criado");
                return;
            }
        }
        Event event = new Event(name);
        Transition transition = new Transition(event,from,to);
        events.add(event);
        transitions.add(transition); 
    }
 
    public void appendTransitionToEvent(String event, String fromState, String toState)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(event))
            {
                Transition transition = new Transition(ev,fromState,toState);
                transitions.add(transition);
                return;
            }
        }
        System.out.println("Evento nao existente");
    }
 
    public void appendTransitionToEvent(String event, String[] fromState, String toState)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(event))
            {
                for(String str : fromState)
                {
                    Transition transition = new Transition(ev,str,toState);
                    transitions.add(transition);
                }
                return;
            }
        }
        System.out.println("Evento nao existente");
    }
 
    public void setGuardToEvent(String event,EventGuard g)
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(event))
            {
                ev.eventguard = g;
                return;
            }
        }
        System.out.println("Evento nao existente");
    }
 
    public boolean fireEvent(String name) throws UndefinedTransitionException, UndefinedStateException, UndefinedEventException
    {
        for(Event ev : events)
        {
            if(ev.getName().equals(name))
            {
                if(ev.eventguard.shouldPerform() == true)
                {
                    ev.getEventCommand().execute(currentstate.getName());
                    for(Transition trs : transitions)
                    {
                        if((trs.triggeredby == ev)&&(trs.from.equals(currentstate.getName())))
                        {
                            for(State st : states)
                            {
                                if(st.getName().equals(trs.to))
                                {
                                    currentstate = st;
                                    notifyObservers();
                                    st.getStateCommand().execute();
                                    return true;
                                }
                            }
                            throw new UndefinedStateException("Estado destino nao existente");
                        }                        
                    }
                    throw new UndefinedTransitionException("Evento nao possui transicao de saida para o estado atual"); 
                }
                else { return false; }
            }                         
        }
        throw new UndefinedEventException("Evento nao existente");        
    }
 
    public String getCurrentStateName()
    {
        return currentstate.getName();
    }
 
}

Classe State:

package Statemachine;
 
class State
{
    private String name;
    private StateCommand statecommand;
 
    public State(String name, StateCommand statecommand)
    {
        this.name = name;
        this.statecommand = statecommand;
    }
 
    public State(String name)
    {
        this.name = name;
        this.statecommand = new StateCommand(null)
        {
            public void execute(){}
        };
    }
 
    public String getName()
    {
        return name;
    }
 
    public StateCommand getStateCommand()
    {
        return statecommand;
    }
}

Classe Event:

package Statemachine;
 
class Event
{
    private String name;
    private EventCommand eventcommand;
    protected EventGuard eventguard = new EventGuard()
    {
        public boolean shouldPerform()
        {
            return true;
        }
    };
 
    public Event(String name, EventCommand eventcommand)
    {
        this.name = name;
        this.eventcommand = eventcommand;
    }
 
    public Event(String name)
    {
        this.name = name;
        this.eventcommand = new EventCommand(null)
        {
            public void execute(String previousState){}
        };
    }   
 
    public String getName()
    {
        return name;
    }
 
    public EventCommand getEventCommand()
    {
        return eventcommand;
    }
}

Classe Transition:

package Statemachine; 
 
class Transition
{
    protected Event triggeredby;
    protected String from;
    protected String to;
 
    public Transition(Event triggeredby, String from, String to)
    {
        this.triggeredby = triggeredby;
        this.from = from;
        this.to = to;
    }
}

Através das classes abstratas StateCommand e EventCommand, pudemos implementar o padrão Command. Observemos que sem o padrão command, haveria a necessidade de uma classe de eventos que possui ação de evento e outra classe de eventos que não possui (idem para o estado). Logo a utilização deste padrão mostra-se altamente recomendável para este tipo de situação.

Por fim, a interface Observer e a classe StateMachine constituem o padrão Observer, muito útil neste problema, já que as aplicações que utilizarem o pacote Statemachine provavelmente precisam ser notificadas na ocorrencia de uma mudança na máquina de estados.

A seguir, verificamos a funcionalidade do pacote Statemachine através de dois testes.
O primeiro é a execução da classe de Testes (fornecida na guia). Segue o resultado do teste:
TestSMSimple.JPG

O segundo teste foi realizado através da criação de outro pacote TesteProfessor, que utiliza os trechos de códigos dados no guia para implementar o cenário Alunos-Professor. Criando uma aplicação para este teste, obtivemos o seguinte resultado:

package TesteProfessor;
import Statemachine.* ;
 
public class Aplicacao
{
    public static void main(String[] args)
    { 
        StateMachine sm = new StateMachine(); 
        Professor professor = new Professor();
        Turma turma = new Turma();
        turma.registerObserver(professor);        
        System.out.println("\nResultado dos metodos fazerProva e prepararAula para divercas ocorrencias na maquina:"); 
        System.out.println("\n");
        professor.updateFromTurma(turma);
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        turma.fire("cancerizar");
        System.out.println(professor.fazerProva()); 
        System.out.println(professor.prepararAula()); 
        System.out.println("\n");
        turma.fire("ser_atenta");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
        System.out.println("\n");
        turma.fire("dormir");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        professor.setGanhandoMilhoesDeDolares(true);
        turma.fire("usar_note");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        turma.fire("dormir");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        professor.setGanhandoMilhoesDeDolares(false);
        turma.fire("usar_note");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
        System.out.println("\n");
        turma.fire("cancerizar");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        turma.fire("ser_atenta");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
        turma.fire("dormir");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula());
        System.out.println("\n");
    }
}

Classe Aplicacao, que executa uma série de transições nas máquinas Turma e ProfessorTestSMProfTurma.JPG

A ultima etapa desta prática consistiu na concepção de uma situação que pudesse ser modelada por uma máquina de Estados Finita, a qual deve ser implementa usando o pacote Statemachine e deve ser testada.
Para tal, foi imaginado o cenário hipotético, em um jogo eletrônico, no qual um determinado personagem, chamado aqui de 'herói', tem sua 'vida' afetada positivamente pelo uso de poções ou negativamente pelo confronto com inimigos. Eis o modelo da tal Maquina:
HeroSM.JPG

A criação desta máquina foi feita na classe Hero:

package My_Application;
 
import Statemachine.*;
 
public class Hero implements Statemachine.Observer
{   
    protected StateMachine sm;
    public Hero(){
        sm = new StateMachine();
        setUpSM();
        sm.registerObserver(this);
    }
 
    private boolean HeroisDead = false;
    private boolean Cursed = false;
    private boolean Invinsible = false;
    public boolean isHeroDead()
    {
        return HeroisDead;
    }
 
    public void setHeroisDead(boolean is)
    {
        HeroisDead = is;
    }
 
    public boolean isCursed()
    {
        return Cursed;
    }
 
    public void setCursed(boolean is)
    {
        Cursed = is;
    }
 
    public boolean isInvincible()
    {
        return Invinsible;
    }
 
    public void setInvincible(boolean is)
    {
        Invinsible = is;
    }
 
    private void setUpSM()
    {
        sm.createState("100%life");
        sm.createState("75%life");
        sm.createState("50%life");
        sm.createState("25%life",new Statemachine.StateCommand(sm)
        {
            public void execute() 
            {
                System.out.println("Danger! Low life."); 
            }
        });
        sm.createState("dead", new Statemachine.StateCommand(sm)
        {
            public void execute() 
            {
                setHeroisDead(true);
                System.out.println("The Hero is dead!!!"); 
            }
        });
        sm.setInitialState("100%life");
 
        String [] from = {"100%life","75%life"};
        sm.createEvent("healedbypotion",from,"100%life");
        sm.appendTransitionToEvent("healedbypotion","50%life", "75%life");
        sm.appendTransitionToEvent("healedbypotion","25%life", "50%life");
        sm.setGuardToEvent("healedbypotion", new Statemachine.EventGuard()
        {
            public boolean shouldPerform()
            {
               return !isCursed(); 
            }
        });
        sm.createEvent("hitbyenemy","100%life","75%life");
        sm.appendTransitionToEvent("hitbyenemy","75%life","50%life");
        sm.appendTransitionToEvent("hitbyenemy","50%life","25%life");
        sm.appendTransitionToEvent("hitbyenemy","25%life","dead");
        sm.setGuardToEvent("hitbyenemy", new Statemachine.EventGuard()
        {
            public boolean shouldPerform()
            {
               return !isInvincible(); 
            }
        });
        sm.createEvent("revivedbymegapotion","dead","50%life",new Statemachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("The Hero has revived!");
            }        
        });
        sm.setGuardToEvent("revivedbymegapotion", new Statemachine.EventGuard()
        {
            public boolean shouldPerform()
            {
               return isHeroDead(); 
            }
        });
    }
 
    public void fire(String eventName){
        try{
          sm.fireEvent(eventName);
        } catch(UndefinedTransitionException e) {
            System.out.println("ERRO: trasição não definida: " + e.getMessage());
        }
    }
 
    public void update(Statemachine.StateMachine sm, String newStateName) {
        System.out.println("Estado atual: " + newStateName);
    }
 
}

Vale ressaltar que, durante a confecção de determinados metodos, foram utilizados classes anônimas na instanciação de ações e condições de guarda. Muito vantajoso neste caso, pois dispença a criação de novas classes.

A classe Aplicação testa a máquina para diversas transiçoes de estado e diferentes condições de guarda:

package My_Application;
 
import Statemachine.* ;
 
public class Aplicacao
{
    public static void main(String[] args)
    { 
        StateMachine sm = new StateMachine(); 
        Hero hero = new Hero();             
        System.out.println("\nResultado apos divercas ocorrencias na maquina:"); 
        System.out.println("--\n");       
        hero.fire("healedbypotion");        
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.setInvincible(true);
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.fire("healedbypotion");        
        System.out.println("--\n");
        hero.setInvincible(false);
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.setCursed(true);
        hero.fire("healedbypotion");        
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.fire("revivedbymegapotion");
        System.out.println("--\n");
        hero.fire("healedbypotion");        
        System.out.println("--\n");
        hero.setCursed(false);
        hero.fire("healedbypotion");
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");
        hero.fire("hitbyenemy");
        System.out.println("--\n");        
    }
}

O resultado esperado desta aplicação encontra-se a seguir:
TestSMHero.JPG

Conclusão

A aparente liberdade na escolha de como implementar o pacote Statemachine certamente enriqueceu esta prática. Pela primeira vez pudemos sentir como, de fato, se concebe um software (guardada as devidas proporções). O guia da prática foi muito explicativo, não havendo problemas quanto as implementações pedidas.
O uso explícito dos padrões citados ajudou na fixação desses conceitos, e a idéia da criação de uma pacote foi muito interessante, ja que nos ajuda a pensar em como a aplicação usuária da nossa maquina de estados se comunica com ela (ou seja, nós definimos até onde o usuário conhece a complexidade do código). Os demais conceitos de OO utilizados nesta prática continuam ajudando aqueles que são novos(como eu) com a linguagem OO à sedimentar todo esse novo conhecimento.

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