Lab3 Alexandre Albizzati

aluno: Alexandre Albizzati
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 06/08/2008 (2)

Introdução

O presente laboratório consistiu no desenvolvimento de um "framework" para a implementação de máquinas de estado. Durante o decorrer do processo, pudemos exercitar não só os padrões de projeto vistos em sala de aula, mas também outros bons hábitos do desenvolvedor: como o tratamento correto de exceções e a busca pelo código desacoplado.
Inicialmente, o "framework" para a máquina de estados foi desenvolvido de acordo com as especificações e testado com o código de testes fornecido. Em seguida, o testes prosseguiram para o exemplo do Professor/Turma, onde pôde-se ver a nossa biblioteca sendo utilizada.
Finalmente, criou-se uma nova situação exemplo onde pôde-se ver a biblioteca em funcionamento.

Desenvolvimento

Conforme definido nas especificações deste laboratório, a classe StateMachine deveria ser a interface pública da máquina de estados. Observando esta interface, podemos perceber que ela tem como responsabilidades, criar e setar, estados e eventos, além de poder setar um estado inicial para a máquina de estados e ainda nos retornar qual o nome do estado em que a máquina se encontra.
Percebemos dessa forma que as "estado" e "evento" são duas entidades importantes, já que terão propriedades e comportamento. Dessa forma, receberam suas próprias classes: "State" e "Event".
Além da função básica de servir como interface pública, a classe StateMachine ficou responsável por "conversar" com nossas novas classes State e Event, delegando para elas as ações correspondentes a estados e eventos, respectivamente.
StateMachine ainda ficou responsável por manter três vetores: um para guardar os eventos possiveis da máquina, um outro para manter os estados dela e, finalmente, um terceiro para guardar os observadores da máquina. O código de StateMachine é mostrado abaixo:

import java.util.*;
 
public class StateMachine
{
// Variaveis utilizadas
    private Vector<Observer> observers = new Vector<Observer>();   // Vetor com os observadores da maquina
    private Vector<State> estados = new Vector<State>();           // Vetor com os estados da maquina
    private Vector<Event> eventos = new Vector<Event>();           // Vetor com os eventos da maquina
    State estado;  // Variavel que armazena o estado atual da maquina
 
    // Construtor
    public StateMachine()
    {
    }
 
//comportamento de observável
    public void registerObserver(Observer observer)
    {
        observers.add(observer);
    }
 
    protected void notifyObservers()
    {
        for(Observer obs : observers)
        {
            obs.update(this,getCurrentStateName());
        }
    }
 
//métodos para a definição da máquina
    public void createState(String name, StateCommand onEnter)
    {
        State estado = new State(name,onEnter);
        estados.add(estado);
    }
 
    public void createState(String name)
    {
        State estado = new State(name);
        estados.add(estado);
    }
 
    private State getStateFromName(String name) throws UndefinedStateException
    {
        for(State state : estados)
        {
            if(name.equals(state.getStateName()))
            {
                return state;
            }
         }
        throw new UndefinedStateException();
    }
 
    public void setInitialState(String name)
    {
        try{
        estado = getStateFromName(name);
        }catch(UndefinedStateException e){};
    }
 
    private Event getEventFromName(String name) throws UndefinedEventException
    {
         for(Event event : eventos)
         {
            if(name.equals(event.getEventName()))
            {
               return event;
            }
         }
         throw new UndefinedEventException();
    }
 
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess)
    {
        try{
        Vector<State> statesFrom = new Vector<State>();
 
        for(String strFrom : from)
        {
            statesFrom.add(getStateFromName(strFrom));
        }
        State stateTo = getStateFromName(to);
        Event evento = new Event(name,statesFrom,stateTo,onSuccess);
        eventos.add(evento);
    }catch(UndefinedStateException e){};
    }
 
    public void createEvent(String name, String[] from, String to)
    {
        try{
        Vector<State> statesFrom = new Vector<State>();
        for(String strFrom : from)
        {
            statesFrom.add(getStateFromName(strFrom));
        }
        State stateTo = getStateFromName(to);
        Event evento = new Event(name,statesFrom,stateTo);
        eventos.add(evento);     
    }catch(UndefinedStateException e){};
    }
 
    public void createEvent(String name, String from, String to,  EventCommand onSuccess)
    {
        try{
        State stateFrom = getStateFromName(from);
        State stateTo = getStateFromName(to);
        Event evento = new Event(name,stateFrom,stateTo,onSuccess);
        eventos.add(evento);       
    }catch(UndefinedStateException e){};
    }
 
    public void createEvent(String name, String from, String to)
    {
        try{        
        State stateFrom = getStateFromName(from);
        State stateTo = getStateFromName(to);
        Event evento = new Event(name,stateFrom,stateTo);
        eventos.add(evento);
    }catch(UndefinedStateException e){};
    }
 
    public void appendTransitionToEvent(String event, String fromState, String toState)
    {
        try{
            Event evento = getEventFromName(event);
            State stateOrigem = getStateFromName(fromState);
            State stateDestino = getStateFromName(toState);
            evento.appendTransition(stateOrigem,stateDestino);
        }catch(UndefinedEventException e){}
        catch(UndefinedStateException e){};
    }
    public void appendTransitionToEvent(String event, String[] fromState, String toState)
    {
        Vector<State> stateOrigem = new Vector<State>();
 
        try{
          Event evento = getEventFromName(event);
          State stateDestino = getStateFromName(toState);
          for(String strFrom : fromState)
          {
               stateOrigem.add(getStateFromName(strFrom));
               evento.appendTransition(stateOrigem,stateDestino);
          }
        }catch(UndefinedEventException e){}
        catch(UndefinedStateException e){};
    }
 
    public void setGuardToEvent(String event,EventGuard g)
    {
        try{
        Event evento = getEventFromName(event);
        evento.setEventGuard(g);
        }catch(UndefinedEventException e){};
    }
 
// métodos para a execução da máquina
    public boolean fireEvent(String name) throws UndefinedTransitionException
    {
        boolean fire = false;
        State proxEstado;
        State previousState;
        try{
        Event evento = getEventFromName(name);
        proxEstado = evento.getNextState(this.estado);   // Se o proxEstado nao for nulo, então devemos realizar a mudança de estados.
 
        if(evento.getEventGuard().shouldPerform())
        {
            fire = true;
            previousState = estado;
            estado = proxEstado;
            notifyObservers();                            // Notifica os observadores sobre o novo estado
            if(evento.getEventCommand()!=null)
            {
               evento.executeEventCommand(previousState);
            }
 
            if(estado.getStateCommand()!=null)
            {
                estado.executeStateCommand();
            }
 
        }
 
        }catch(UndefinedEventException e){throw new UndefinedTransitionException();}
        catch(UndefinedStateException e){};
 
        return fire;        
    }
 
    public String getCurrentStateName()
    {
        return estado.getStateName();
    }
}

É importante observar que foram criados dois métodos privados em StateMachine: getStateFromName() e getEventFromName(). Ambos recebem como parâmetro uma String e retornam um estado/evento para que as classes State e Event possam trabalhar diretamente com o estado/evento e não com as Strings deles.
Outra importante função de StateMachine é a de ser a classe responsável pelo disparo de eventos, via método fireEvent(). Este método recebe uma string correspondente a um evento, encontra o evento, procura a transição correta, se ela não existir lança um exceção. Se existir realiza a transição e atualiza o estado da máquina, além de notificar os observadores da mesma sobre a sua mudança de estado.

Vejamos agora a classe State:

public class State
{
    // instance variables - replace the example below with your own
    private String name;
    private StateCommand onEnter;
 
    public State(String name, StateCommand onEnter)
    {
        this.name = name;
        this.onEnter = onEnter;
    }
 
    public State(String name)
    {
        this.name = name;
        this.onEnter = null;
    }
 
    public void executeStateCommand()
    {
        onEnter.execute();
    }
 
    public String getStateName()
    {
        return name;
    }
 
    public StateCommand getStateCommand()
    {
        return onEnter;
    }
}

Esta classe guarda o nome do estado e um possível StateCommand dele. Além disso, é ela que, de fato, realiza os comandos para criar estados.

Vejamos agora a classe Event:

import java.util.*;
 
public class Event
{
    // instance variables - replace the example below with your own
    private String name;
    private EventCommand onSuccess;
    private EventGuard g = new EventGuard(){
        public boolean shouldPerform()
        {
          return true;  
        }
    };
    private Vector<State> estadosOrigem = new Vector<State>();
    private Vector<State> estadosDestino = new Vector<State>();
 
    public Event(String name,Vector<State> from, State to, EventCommand onSuccess)
    {
        this.name = name;
        appendTransition(from,to);
        this.onSuccess = onSuccess;
    }
 
    public Event(String name,Vector<State> from, State to)
    {
        this.name = name;
        appendTransition(from,to);
        this.onSuccess = null;
    }
 
    public Event(String name, State from, State to)
    {
        this.name = name;
        appendTransition(from,to);
        this.onSuccess = null;
    }
 
    public Event(String name, State from, State to,EventCommand onSuccess)
    {
        this.name = name;
        appendTransition(from,to);
        this.onSuccess = onSuccess;
    }
 
    public void appendTransition(State from,State to)
    {
        estadosOrigem.add(from);
        estadosDestino.add(to);
    }
 
    public void appendTransition(Vector<State> from,State to)
    {
        for(State stateFrom : from)
        {
            this.estadosOrigem.add(stateFrom);  
            this.estadosDestino.add(to);
        }
    }    
 
    public State getNextState(State estadoAtual) throws UndefinedTransitionException
    {
        int indice;
        indice = estadosOrigem.indexOf(estadoAtual);  // acha a primeira ocorrencia de estado (usando equals) em estadosOrigem
        if(indice == -1) throw new UndefinedTransitionException();
        return estadosDestino.get(indice);
    }
 
    public String getEventName()
    {
        return name;
    }
 
    public EventCommand getEventCommand()
    {
        return this.onSuccess;
    }
 
    public void setEventGuard(EventGuard g)
    {
        this.g = g;
    }    
 
    public void executeEventCommand(State previousState)
    {
        this.onSuccess.execute(previousState.getStateName());
    }
 
    public EventGuard getEventGuard()
    {
        return this.g;
    }

Esta classe é responsável não só por manter o nome do evento e seus métodos, mas também por manter a estrutura de dados responsável por guardar os estados de origem e destino para cada evento. A estrutura escolhida foi dois vetores paralelos: estadosOrigem e estadosDestino. Elementos de um mesmo indice formam um par origem-destino. Esta estrutura de dados foi utilizada, principalmente pelo método getNextState, que recebe como parâmetro o estado atual e então procura ele no vetor estadosOrigem, retornando o elemento do vetor estadosDestino com o índice correspondente.
No caso de não encontrar uma transição definida, getNextState() lança uma exceção do tipo UndefinedTransitionException. Esta função é usada dentro da função fireEvent em StateMachine.

Testes

Abaixo mostramos o diagrama de classes obtido após a inserção de todas as classes:

classes.JPG

O resultado dos testes para a classe StateMachine:

testeStateMachine.JPG

Mostramos agora os resultados dos testes com o exemplo Professor/Turma:
Inicialmente criamos o professor e a turma:

criaProf.JPG

Em seguida registramos o professor como observador da turma e começamos a realizar os testes. Inicialmente temos que o estado do professor é motivado:

estadoProf.JPG

Em seguida fazemos os alunos dormirem:

fire.JPG
fire2.JPG
E o resultado:resultFire.JPG

Com o novo estado do professor:

estadoDesmotivado.JPG

Ao continuar fazendo os testes, observei ainda que quando desmotivamos mais ainda o professor, ele se torna irado. Os testes mostraram ainda como o professor age de maneira diferente ao fazer as provas, de acordo com o estado em que se encontra. Foi possível ver ainda que quando setamos setGanhandoMilhoesDeDolares como true, o professor não se desanima mais.

Testes do Exemplo a escolher:

O exemplo que acabei escolhendo foi uma máquina de estados para simular o comportamento da população e como isso afetava o estado do mundo em que ela vive.
A descrição do problema é a seguinte: "uma população pode ter 6 ações diferentes com relação ao mundo em que ela vive, podendo: poluir, desmatar, destruir, preservar, reciclar e reflorestar. Quando a população realiza o evento de poluir, vai para o estado poluindo, quando realiza desmatar, vai para o estado desmatando, e assim por diante. É permitido que a população transite entre quaisquer desses estados, sem nenhuma restrição. As ações que a população realiza geram impacto no mundo. O mundo possui quatro estados possíveis: mundo equilibrado, mundo seculo XX, mundo de mad max e apocalipse. O mundo pode somente piorar ou melhorar. Quando a população polui, desmata ou destrói, o mundo piora. Nos outros casos o mundo melhora. Contudo, há algumas restrições: se o mundo já estiver em mundo de mad max ou em apocalipse, o mundo não pode mais melhorar."
O diagrama de classes obtido para esse exemplo é mostrado abaixo:

diagramaExemplo.JPG

O código-fonte das classes é mostrado abaixo:

Classe Populacao:

import stateMachine.*;
import java.util.Vector;
public class Populacao extends WithSM
{
    private Vector<PopulacaoObserver> observers = new Vector<PopulacaoObserver>();
 
    public Populacao() {
         super();
 
         sm.createState("poluindo");
         sm.createState("desmatando");
         sm.createState("destruindo");
         sm.createState("preservando");
         sm.createState("reciclando");
         sm.createState("reflorestando");
         sm.setInitialState("poluindo");
         String from[] = { "poluindo", "desmatando", "destruindo","preservando","reciclando","reflorestando"};
         sm.createEvent("poluir", from, "poluindo");
         sm.createEvent("desmatar", from, "desmatando");
         sm.createEvent("destruir", from, "destruindo");
         sm.createEvent("preservar", from, "preservando");
         sm.createEvent("reciclar", from, "reciclando");
         sm.createEvent("reflorestar", from, "reflorestando");
    }
 
    public void registerObserver(PopulacaoObserver observer){
        observers.add(observer);
    }
 
    private void notifyObservers() {
        for(PopulacaoObserver obs : observers) {
            obs.updateFromPopulacao(this);
        }
    }
 
    public void fire(String event) {
        super.fire(event);
        notifyObservers();
    }
}

Classe Mundo:

public class Mundo extends WithSM implements stateMachine.Observer, PopulacaoObserver
{
 
    public Mundo() {
        super();
        setUpSM();
        sm.registerObserver(this);
 
    }
 
    public String verMundo() {
        if(getState() == "mundo equilibrado")
            return "O mundo é bonito! Natureza equilibrada!";
        else if(getState() == "mundo seculo XX")
            return "Industrias por todo lado!";
        else if(getState() == "mundo de mad max")
            return "Só deserto...";
        else
            return "Que mundo??";
    }
 
    public void update(stateMachine.StateMachine sm, String newStateName) {
        System.out.println("Estado do mundo: " + newStateName);
    }
 
    public void updateFromPopulacao(Populacao p) {
        if (p.getState() == "poluindo" || p.getState() == "desmatando" || p.getState() == "destruindo")
            this.fire("piorar");
        else
            this.fire("melhorar");
    }
 
    private void setUpSM() {
 
        //criando estados
        sm.createState("mundo equilibrado");
        sm.createState("mundo seculo XX");
        sm.createState("mundo de mad max");
        sm.createState("apocalipse", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("O mundo acabou!!");
            }
        } );
        sm.setInitialState("mundo equilibrado");
 
        //evento piorar
        sm.createEvent("piorar", "mundo equilibrado", "mundo seculo XX", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("O mundo está sendo destruido!");
            }
        });
        sm.appendTransitionToEvent("piorar", "mundo seculo XX", "mundo de mad max");
        sm.appendTransitionToEvent("piorar", "mundo de mad max", "apocalipse");
        sm.appendTransitionToEvent("piorar", "apocalipse", "apocalipse");
 
        //evento melhorar
        sm.createEvent("melhorar", "mundo seculo XX", "mundo equilibrado", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Um mundo melhor!");
            }
        });
        sm.appendTransitionToEvent("melhorar", "mundo equilibrado", "mundo equilibrado");
        sm.appendTransitionToEvent("melhorar", "mundo seculo XX", "mundo equilibrado");
        sm.appendTransitionToEvent("melhorar", "mundo de mad max", "mundo de mad max");
        sm.appendTransitionToEvent("melhorar", "apocalipse", "apocalipse");
        sm.setGuardToEvent("melhorar", new stateMachine.EventGuard() {
            public boolean shouldPerform(){
                if(getState() == "mundo de mad max" || getState() == "apocalipse")
                {
                    System.out.println("O mundo não tem mais volta!");
                    return false;
                }
                return true;
            }
        });
 
    }
 
}

Segue abaixo o resultado de alguns testes.
Inicialmente, criamos o mundo Terra e a população Povo. Então registramos Terra como observador de Povo.

f1.JPG

Em seguida, começamos a disparar os eventos do Povo e observar as reações que eles causavam na Terra.
Os eventos colocados foram na seguinte ordem:
1) destruir
2) reciclar
1) poluir

A saída após estes três eventos é a seguinte:

f2.JPG

Utilizamos agora, neste estado, o método verMundo():

f3.JPG

Agora vamos destruir o mundo mais uma vez. Observamos que o mundo vira um mundo de mad max, e o resultado de verMundo() é:

f4.JPG

E agora lançamos o método preservar, e a resposta é que o mundo não tem mais volta:

f5.JPG

Se poluirmos um pouco mais o mundo, atingimos o apocalipse:

f6.JPG

Conclusão

Este laboratório explorou muitos conceitos da OO. Além de ter exercitado os padrões de projetos vistos na teoria, nos colocou diante de uma situação em que era dada uma especificação e a partir disso deveriamos desenvolver as classes conforme achássemos mais adequado. Assim, foi necessário aplicar os conceitos de encapsulamento e desacoplamento, além de tratamento de exceções e aplicação de testes.
Também foi interessante observar como o desacoplamente é importante no processo de desenvolvimento de softwares, pois o "framework" da máquina de estados pôde ser utlizado em pelo menos duas aplicações diferentes, sem a necessidade de alterar nada no código do pacote stateMachine.

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