Lab3 Cicero David

aluno: Nome do Aluno
ano/sem: 2008/2o.
data do laboratório (6ª) : 10/09/2008 (3)

Introdução

Nessa prática implementamos um biblioteca que simula uma máquina de estados. Em seguida utilizamos essa biblioteca para criarmos um cenário que simboliza o ralacionamento entre alunos e professores. Em seguida foi modedo um cenário de uma maquina de estados de um contador que acionava a explosão de uma bomba.

Desenvolvimento

Inicialmente, definiu-se um as classes e as relações entre elas, resultando no diagrama abaixo. Tal resultado foi obtido sem a utlização do diagrama lógico disponibilizado para metade da sala.

imagem2.JPG

Um objeto da classe transição contém somente a informação sobre o estado de origem e o estado de destino de uma determinada trasição. O código é bastante simples e mostrado abaixo:

package stateMachine;
 
public class Transicao {
 
    String from;
    String to;
    public Transicao(String from, String to) {
 
        this.from = from;
        this.to = to;
 
    }
}

Em seguida, criou-se uma classe Evento, que possuia informações sobre as transições que o evento causa, armazenadas em um HashMap, além de possuir as informações do evento de comando e evento de guarda.

package stateMachine;
 
import java.util.HashMap;
 
public class Evento {
    protected String nome;
    protected EventCommand evtCommand;
    protected HashMap transicoes = new HashMap();
    public EventGuard evtGuard = new EventGuard() {
        public boolean shouldPerform() {
            return true;
        }
    };
 
    public Evento(String nome) {
        this.nome = nome;
        this.evtCommand = new EventCommand(null) {
 
            public void execute(String previousState) {
            }
        };
    }
 
    public Evento(String nome, String from, String to) {
        this.nome = nome;
        this.transicoes.put(from, new Transicao(from, to));
        this.evtCommand = new EventCommand(null) {
 
            public void execute(String previousState) {
            }
        };
 
    }
 
    public Evento(String nome, String from, String to, EventCommand evtCommand) {
        this.transicoes.put(from, new Transicao(from, to));
        this.nome = nome;
        this.evtCommand = evtCommand;
 
    }
 
    public Evento(String nome, EventCommand evtCommand) {
        this.nome = nome;
        this.evtCommand = evtCommand;
    }
 
    public void AddTransicao(String fromState, String toState) {
        if (!transicoes.containsKey(fromState)) {
            transicoes.put(fromState, new Transicao(fromState, toState));
        }
 
    }
}

A classe estado foi criada para compor a lista de estados criada para a máquina de estados. Contém basicamente o nome e comando de estado. Note que poderia ser feito alguma relação entre a classe Estado e Transicao ou Evento. O evento poderia conter informação sobre quais estados atua. Um trasição poderia contem objetos de Estado para definir o estado de origem e destino, e não somente o nome dos estados como mostrado no código de Transicao. Entretanto, isso não foi realizado por achar que tal implementação seria mais complicada e causaria maior acoplamento.
Abaixo temos o códico de Estado.

package stateMachine;
 
class Estado {
 
    private String nome;
    private StateCommand stateCommand;
 
    public StateCommand getStateCommand() {
        return stateCommand;
    }
 
    public String getNome() {
        return nome;
    }
 
    public Estado(String nome, StateCommand stateCommand) {
        this.nome = nome;
 
        this.stateCommand = stateCommand;
    }
 
     public Estado(String nome) {
        this.nome = nome;
        this.stateCommand = new StateCommand(null) {
 
            public void execute() {
            }
        };
    }
 
}

A classe central da máquina de estados é a StateMachine, que contém a maior parte das responsabilidades da implementação. Foi utilizado um HashMap para armazenar os eventos e os estados criados. Isso facilitou bastante, pois eliminou a necessidade de realizar busca em vetores, pois porvavelmente seria usado um Vector<> ou List, assim cada vez que se quisesse alcançar determinado evento ou estado teria que ser feito uma busca pelo nome. Por outro lado lado a utilização do HashMap dificultou um pouco por surgir a necessidade de realizar diversos casts, como pode ser notado no método fireEvent da classe abaixo. Isso acaba dificultando também o entendimento do código.

package stateMachine;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
 
public class StateMachine {
    //comportamento de observável
 
    private Estado estadoAtual;
    private Vector<Observer> observers = new Vector<Observer>();
    private HashMap estados = new HashMap();
    private HashMap eventos = new HashMap();
 
    public void registerObserver(Observer observer) {
 
        observers.add(observer);
 
    }
 
    protected void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this, estadoAtual.getNome());
 
        }
    }
 
    //métodos para a definição da máquina
    public void createState(String name, StateCommand onEnter) {
        if (!this.estados.containsKey(name)) {
            estados.put(name, new Estado(name, onEnter));
        } else {
            System.out.println("Estado já criado");
        }
    }
 
    public void createState(String name) {
        if (!this.estados.containsKey(name)) {
            estados.put(name, new Estado(name));
        } else {
            System.out.println("Estado já criado");
        }
    }
 
    public void setInitialState(String name) {
        this.estadoAtual = (Estado) estados.get(name);
    }
 
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess) {
 
        if (!this.eventos.containsKey(name)) {
 
            this.eventos.put(name, new Evento(name, onSuccess));
            appendTransitionToEvent(name, from, to);
 
        } else {
            System.out.println("Evento já criado anteriormente");
 
        }
 
    }
 
    public void createEvent(String name, String[] from, String to) {
 
        if (!this.eventos.containsKey(name)) {
 
            this.eventos.put(name, new Evento(name));
            appendTransitionToEvent(name, from, to);
 
        } else {
            System.out.println("Evento já criado anteriormente");
        }
    }
 
    public void createEvent(String name, String from, String to, EventCommand onSuccess) {
        if (!this.eventos.containsKey(name)) {
            this.eventos.put(name, new Evento(name, from, to, onSuccess));
        } else {
            System.out.println("Evento ja criado anteriromente");
        }
 
    }
 
    public void createEvent(String name, String from, String to) {
        if (!this.eventos.containsKey(name)) {
            this.eventos.put(name, new Evento(name, from, to));
        } else {
            System.out.println("Evento ja criado anteriromente");
        }
    }
 
    public void appendTransitionToEvent(String event, String fromState, String toState) {
        if (eventos.containsKey(event)) {
            ((Evento) eventos.get(event)).AddTransicao(fromState, toState);
        } else {
            System.out.println("Evento nao criado ainda");
        }
    }
 
    public void appendTransitionToEvent(String event, String[] fromState, String toState) {
 
        if (eventos.containsKey(event)) {
 
            for (String str : fromState) {
                ((Evento) eventos.get(event)).AddTransicao(str, toState);
            }
        } else {
            System.out.println(
                    "Evento nao criado ainda");
        }
    }
 
    public void setGuardToEvent(String event, EventGuard g) {
 
        if (eventos.containsKey(event)) {
            ((Evento) eventos.get(event)).evtGuard = g;
        }
    }
 
    // métodos para a execução da máquina
    public boolean fireEvent(String name) throws UndefinedTransitionException, UndefinedEventException {
 
        if (eventos.containsKey(name)) {//verifica se o evento existe
            if (((Evento) eventos.get(name)).evtGuard.shouldPerform()) { //verifica a condicao de guarda
                if (((Evento) eventos.get(name)).transicoes.containsKey(this.estadoAtual.getNome())) { //verica se o evento tem destino para o estado atual
                    boolean contemTo = false;
                    List vectorEstados = new ArrayList(estados.values());
                    for (Estado estado : (ArrayList<Estado>) vectorEstados) {
                        //verifica se o estado destino existe
                        if ((((Transicao) (((Evento) eventos.get(name)).transicoes.get(this.estadoAtual.getNome()))).to).equals(estado.getNome())) {
                            contemTo = true;
                        }
                    }
                    if (!contemTo) {
                        throw new UndefinedStateException("Estado destino nao definido");
                    } else {
                        ((Evento) eventos.get(name)).evtCommand.execute(estadoAtual.getNome()); //executa o eventCommand
                        //define o novo estado atual
                        this.estadoAtual = (Estado) estados.get((((Transicao) (((Evento) eventos.get(name)).transicoes.get(this.estadoAtual.getNome()))).to));
                        this.estadoAtual.getStateCommand().execute(); //realiza o stateCommando do novo estado
 
                    }
                } else {
                    throw new UndefinedTransitionException("Evento nao possui transicao de saida para o estado atual");
                }
                return true;
            } else {
                return false;
            }
        } else {
            throw new UndefinedEventException("Evento nao definido");
        }
    }
 
    public String getCurrentStateName() {
        return this.estadoAtual.getNome();
    }
}

Uma outra forma de implementar o máquina de estados, do ponto de vista das classes e relacionamentos, seria eliminar a classe Evento, passanto todas as suas responsabilidades para Transicao. Assim StateMachine se relacionaria diretamento com a classe Transicao, através de uma lista com todas as transiçõe posiveis da máquina. Cada Transicao possui informações sobre o estado de origem e destino, evento e os comandos de gurada e evento. Isso reduziria o numero de classes entretando sobrecarregaria a classe Transicao com muitas responsabilidades. Também seria dificuldado a acesso um determinada transição, pois seria necessário realizar uma busca por evento e estado de origem.

Outra forma seria relacionar StateMachine com Transicao, a qual se relacionaria com Evento. Cada transição teria um evento e cada SteteMachine teria uma lista de transições.

Após implementado a biblioteca da máquina de estados, realizamos o teste proposto, obtendo o resultado abaixo:

testeJu.JPG

Com o resultado, pode-se validar a biblioteca da máquina de estados.

A próxima etapa foi implementar uma série de eventos no cenário de alunos e professores (cujo código já é criado na instrução do laboratório). Para tanto, implementamos e executamos a classe abaixo:

package lab3;
 
import stateMachine.*;
import cenarioEscola.*;
public class TesteEscola {
 
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
 
        StateMachine sm = new StateMachine();
 
        Professor professor = new Professor();
        Turma turma = new Turma();
        turma.registerObserver(professor);
 
        System.out.println("***\nO professor inicialmente está motivado");
        System.out.println(professor.fazerProva());    
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma inicialmente estava dormindo, o professor se desmotiva");
        professor.updateFromTurma(turma);
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma canceriza, o professor se motiva");
        turma.fire("cancerizar");
        System.out.println(professor.fazerProva()); 
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma usa note book, o professor se desmotiva");
        turma.fire("usar_note");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma dorme, o professor se desmotiva e não volta mais para motivado");        
        turma.fire("dormir");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma usa note book, o professor se desmotiva, indo para o estado vacinado");
        turma.fire("usar_note");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma canceriza, o professor se motiva, indo pro estado irado");
        turma.fire("cancerizar");
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
 
        System.out.println("***\nA turma canceriza, o professor se motiva, mas continua no estado irado");
        professor.updateFromTurma(turma);
        System.out.println(professor.fazerProva());  
        System.out.println(professor.prepararAula()); 
}
}

A execução da classe acima já exibe o resultado esperado e o resultado obtido, como mostra a figura abaixo:

tetsteEscola.JPG

A última fase do lab foi criar um novo cenário para a máquina de estados. Foi escolhido então implementar um cenário contendo um contador de 0 a 3 e uma bomba de que explode quando o contador chegar a 3. O contador possui os eventos de "incrementar" e "decrementar". Além disso, pode-se travar o contador, não sendo possível nem incrementar e decrementar o contador. O contador é cíclico.
A implementação é semelhante ao do cenário da Escola. Utilizou-se a mesma classe WithSM e criou-se mais as classes Contador e Bomba mostrados abaixo, além da interface ContadorObserver.

package Contador;
 
import java.util.Vector;
import stateMachine.*;
public class Contador extends WithSM {
 
    boolean travado = false;
    Vector<ContadorObserver> observers= new Vector<ContadorObserver>();
   // StateMachine sm = new StateMachine();
 
    public Contador() {
 
        super();
        sm.createState("0", new StateCommand(sm){
            public void execute(){
                System.out.println("0");
            }
        });
        sm.createState("1", new StateCommand(sm){
            public void execute(){
                System.out.println("1");
            }
        });
        sm.createState("2", new StateCommand(sm){
            public void execute(){
                System.out.println("2");
            }
        });
        sm.createState("3", new StateCommand(sm){
            public void execute(){
                System.out.println("3");
            }
        });
        sm.createState("4", new StateCommand(sm){
            public void execute(){
                System.out.println("4");
            }
        });
 
        sm.createEvent("incrementa", "0", "1", new EventCommand(sm){
             public void execute(String previousState){
                 System.out.println(previousState + "++=" );
             }
        });
        sm.appendTransitionToEvent("incrementa", "1", "2");
        sm.appendTransitionToEvent("incrementa", "2", "3");
        sm.appendTransitionToEvent("incrementa", "3", "0");
 
        sm.createEvent("decrementa", "0", "3", new EventCommand(sm){
             public void execute(String previousState){
                 System.out.println(previousState + "--=" );
             }
        });
        sm.appendTransitionToEvent("decrementa", "3", "2");
        sm.appendTransitionToEvent("decrementa", "2", "1");
        sm.appendTransitionToEvent("decrementa", "1", "0");
 
        sm.setGuardToEvent("incrementa", new EventGuard(){
            public boolean shouldPerform(){
                 return !isTravado();
             }
        });
 
         sm.setGuardToEvent("decrementa", new EventGuard(){
            public boolean shouldPerform(){
                 return !isTravado();
             }
        });
 
    }
 
     public void registerObserver(ContadorObserver observer){
        observers.add(observer);
    }
 
    private void notifyObservers() {
        for(ContadorObserver obs : observers) {
            obs.updateFromContador(this);
        }
    }
 
    public void fire(String event) {
        super.fire(event);
        notifyObservers();
    }
 
    private boolean isTravado(){
        return travado;
    }
 
    public void zerar( ){
        sm.setInitialState("0");
 
    }
 
    public void travar(){
        travado = true;
    }
 
}
package Contador;
 
public class Bomba implements ContadorObserver {
 
    public void updateFromContador(Contador contador) {
        System.out.println("O tempo atual é:" + contador.getState());
        if (contador.getState().equals("3")) System.out.println("Explodiu");
    }
 
}

Por fim realizamos um teste que simula o cenário do contador e bomba através da classe abaixo:

package lab3;
 
import Contador.*;
 
public class TesteContador {
 
    public static void main(String[] args){
 
        Contador contador = new Contador();
        contador.zerar();
        Bomba bomba = new Bomba();
        contador.registerObserver(bomba);
 
        System.out.println("Conta até 3 e explode");
        contador.fire("incrementa");
        contador.fire("incrementa");
        contador.fire("incrementa");
 
        System.out.println("****\n Decrementa até zero");
        contador.fire("decrementa");
        contador.fire("decrementa");
        contador.fire("decrementa");
 
        System.out.println("****\n Conta ate 2 e trava o contador");
        contador.fire("incrementa");
        contador.fire("incrementa");
        contador.travar();
        contador.fire("incrementa");
        contador.fire("incrementa");
        contador.fire("decrementa");
        contador.fire("decrementa");
 
    }
 
}

Obtivemos o resultado satisfatório abaixo:

testeContador.JPG

Conclusão

Considero a prática bastante enriquecedora, com muitos conseitos aprendidos. Porém foi longa, sendo necessário umas 12 horas, incluindo o tempo para fazer o relatório. Acho que poderia ser dividida.
Pudemos ver que a implementação do padrão Observer é bastante útil e permitiu um encapsulamento e desacoplamento, pois permitiu a criação de um pacote universal que implementa uma máquina de estados.

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