Lab3 Marcus Leandro

aluno: Marcus Leandro Rosa Santos
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 06/08/2008 (2)

Introdução

Nesse laboratório tivemos enfoque na abordagem do padrão Observer e da manipulação de excessões, obserado no metodo fireEvent da maquina de estados. Foi aprendido a utilização e funcionalidade de classes anônimas, também foi feito uso de encapsulamento. O propósito deste lab consistiu na modelagem do projeto tendo algumas especificações, dadas nas instruções do laboratório. É claro que também fizemos usos dos principais conceitos de OO, tais como polimorfismos, heranças, controle de acesso e dentre outros.

Desenvolvimento

Inicialmente, foi implementado o pacote stateMachine, cujas especificações estão no relatório. A tal pacote, foi por mim adicionado algumas classes extras que julquei inseri-las para melhorar a implementação do projeto. Tais calsses são: Estado, Evento e Transicao.

Na classe Evento, criei três variáveis de instâncias: String name (que guarda o nome do evento), EventCommand eventoComando (que guarda o EventCommand associado ao estado) e ArrayList<Transicao> transicaoList (que guarda a lista de transições possiveis para esse envento. Abaixo está o código fonte da classe Evento.

package stateMachine;
 
import java.util.ArrayList;
 
public class Evento {
 
    //variáveis de intancias:
 
    protected String name;
    protected EventCommand eventoComando; //tem valor nulo (valor padrão), pois não foi inicializado.
    protected EventGuard eventoGuard; //análogo ao anterior.
    protected ArrayList<Transicao> transicaoList = new ArrayList<Transicao>();    
 
    //construtor:
 
    public Evento(String name){
        this.name = name;
    }
 
    //metodos:
 
    public void setEventCommand(EventCommand e){
        eventoComando = e;
    }
 
    public void setEventGuard(EventGuard e){
        eventoGuard = e;
    }
 
    public void addTransicao(Transicao trans){
        transicaoList.add(trans);
    }    
}

Já na classe estado, precisei apenas guardar como variáveis de instâncias o nome do estado cuja variável é "String name" e um StateCommand na variável "StateCommand estadoComando". Ao se instanciar essa classe deve-se entrar com o nome do estado, logo o construtor recebe uma String que é o nome da classe e temos no corpo da classe apenas um método que seta o StateCommand da classe. Veja o código fonte dela abaixo.

package stateMachine;
 
public class Estado{
 
    //variáveis de intancias:
    protected String name;
    protected StateCommand estadoComando = null; //não precisava declarar nulo, mas coloquei pra reforçar
 
    //construtor:
    public Estado(String name){
        this.name = name;
    }
    //métodos:
    public void setStateCommand(StateCommand e){
        estadoComando = e;
    }    
}

Por fim, a última classe extra é a classe Transição. Esta classe tem apenas duas variáveis de instâncias, uma indicando o estado atual, isto é, o estado que deve ecorrer a transição e outra variável de isntância cujo nome é
estadoTo, que guarda o nome do estado que do estado atual, pode-se transitar. O c[odigo desta classe encontra-se abaixo:

package stateMachine;
 
public class Transicao {
 
    //variáveis de instância:
 
    protected String nomeDoEstadoOrigem;
    protected String estadoTo;
 
    //Contrutor:
 
    public Transicao(String nomeDoEstado, String estadoTo){
        this.nomeDoEstadoOrigem = nomeDoEstado ;
        this.estadoTo  = estadoTo;
    }    
}

Observe que o construtor já guarda as duas variáveis de interesse: nomeDoEstadoOrigem e estadoTo, portanto, para o objetico em questão não precisamos de metódo nenhum essa classe.

Vamos agora para a classe StateMachine.
Nessa classe é onde encontramos todas as funcionalidades da nossa máquina de estados. Podemos dizer que todas as outras classes e interfaces são auxiliares, visando melhorar o padrão do projeto. Em tal classe podemos encontrar métodos para:

  1. Registrar um dado observador;
  2. Notificar a lista de observadores quando um evento é disparado;
  3. Criar estados;
  4. Setar o estado inicial;
  5. Criar Eventos;
  6. definir um eventa ja criado setando outra transição, diferenta da anterior;
  7. Setar o evento de guarda;
  8. Disparar evento;
  9. Obter o estado atual.

Ora, para a implementação do método observer utilizamos a interface Observer cujo método é
void update(StateMachine sm, String newStateName). O código da interfacce foi dado na descrição do lab.

Também utilizei um método a mais( private void VerificarEstado(String[] strings) ) que não estava na descrição dessa classe. Tal método tinha como função verificar se uma lista de estados entrado como parâmetro na criação de um evento existia. Tal método foi útil na implementação dos métodos creatEvent e appendTransitionToEvent, pois não tem sentido eu criar um evento ou adicionar uma nova transição a um evento que vá ou inicie em um estado que não existe. Ao se verificar que um dado estado entrado como parâmetro não existia, lançou-se a excessão UndefinedTransitionException. Cabe ressaltar também que o método fireEvent(String nameEvent) laça uma excessão do tipo UndefinedEventException() toda vez que o eventro entrado como parãmetro não existir, ela também dispara o metodo execute do eventoComando para esse evento se ele não for null, antes dele executar o evento e também dispara o método execute de estadocomando depois da transição, se ele for realizada, é claro. O corpo da classe encontra-se abaixo:

package stateMachine;
import java.util.*;
 
public class StateMachine {
 
    //instancias:
 
    private ArrayList<Observer> observerList = new ArrayList<Observer>();
    private ArrayList<Evento> eventoList = new ArrayList<Evento>();
    private ArrayList<Estado> estadoList = new ArrayList<Estado>();    
    String nomeDoEstadoAtual;
 
    //comportamento de observável
 
    public void registerObserver(Observer observer){
        observerList.add(observer);
    }
 
    protected void notifyObservers(){   
        for( Observer auxiliar : observerList ){            
             auxiliar.update(this,nomeDoEstadoAtual);           
        }
    }    
 
    //métodos para a definição da máquina:
 
    public void createState(String name, StateCommand onEnter){
        estadoList.add(new Estado(name));
        for(Estado estado : estadoList){
            if(estado.name == name)
                estado.setStateCommand(onEnter);
        }
    }
 
    public void createState(String name){
        estadoList.add(new Estado(name));
    }
 
    public void setInitialState(String name){
        boolean ok = false;
        for(Estado estado : estadoList){
            if(estado.name == name){
                nomeDoEstadoAtual = name;
                ok = true;    
                break;
            }
        }
        if(ok == false){
            throw new UndefinedStateException();
        }
    }
 
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess){     
        VerificarEstado(from);        
        String[] aux = {to};        
        VerificarEstado(aux);       
 
        eventoList.add( new Evento(name) );     
        for( Evento evento : eventoList ){
            if(evento.name == name){
                evento.setEventCommand(onSuccess);                              
                for(String st : from)                   
                    evento.addTransicao(new Transicao(st,to));              
            }
        }       
    } 
 
    public void createEvent(String name, String[] from, String to){     
        VerificarEstado(from);
        String[] aux = {to};
        VerificarEstado(aux);
 
        eventoList.add( new Evento(name) );     
        for( Evento evento : eventoList ){
            if(evento.name == name){           
                for(String st : from)                   
                    evento.addTransicao(new Transicao(st,to));              
            }
        }       
    } 
 
    public void createEvent(String name, String from, String to,  EventCommand onSuccess){    
        String[] aux = {from , to};
        VerificarEstado(aux);
 
        eventoList.add( new Evento(name) );     
        for( Evento evento : eventoList ){
            if(evento.name == name){
                evento.setEventCommand(onSuccess);              
                evento.addTransicao(new Transicao(from,to));
                break;
            }
        }
    }
 
    public void createEvent(String name, String from, String to){       
        String[] aux ={from , to};    
        VerificarEstado(aux);
 
        eventoList.add( new Evento(name) );     
        for( Evento evento : eventoList ){
            if(evento.name == name){                                
                evento.addTransicao(new Transicao(from,to));     
                break;
            }
        }
    }
 
    public void appendTransitionToEvent(String event, String fromState, String toState){
        String[] aux = {fromState , toState};
        VerificarEstado(aux);
        for( Evento evento : eventoList ){
            if(evento.name == event){                                           
                evento.addTransicao(new Transicao(fromState,toState));
                break;
            }
        }
    }
 
    public void appendTransitionToEvent(String event, String[] fromState, String toState){              
        VerificarEstado(fromState);
        String[] aux = {toState};
        VerificarEstado(aux);
        for( Evento evento : eventoList ){
            if(evento.name == event){                                           
                for(String st : fromState)                  
                    evento.addTransicao(new Transicao(st,toState));
                break;
            }
        }
    }
 
    public void setGuardToEvent(String event,EventGuard g){
        for( Evento evento : eventoList ){
            if(evento.name == event){
                evento.setEventGuard(g);
                break;
            }
        }
    }  
 
   // métodos para a execução da máquina
 
    public boolean fireEvent(String name) throws UndefinedTransitionException{      
        Evento eventoAuxiliar = null ;
        for(Evento evento : eventoList){
            if(evento.name == name){
                eventoAuxiliar = evento;
                break;
            }
        }
        if(eventoAuxiliar == null){         
            throw new UndefinedEventException();
        }
        else{
            if( (eventoAuxiliar.eventoGuard == null) || eventoAuxiliar.eventoGuard.shouldPerform() ){
                if(eventoAuxiliar.eventoComando != null)
                    eventoAuxiliar.eventoComando.execute(nomeDoEstadoAtual);
 
                boolean ok = false;
                for(Transicao transicion : eventoAuxiliar.transicaoList){           
                    if(transicion.nomeDoEstadoOrigem == nomeDoEstadoAtual ){
                        nomeDoEstadoAtual = transicion.estadoTo;
                        for(Estado estado : estadoList){
                            if(estado.name == nomeDoEstadoAtual){
                                if(estado.estadoComando !=null)
                                    estado.estadoComando.execute();
                                break;
                            }
                        }
                        notifyObservers();
                        ok = true;
                        break;
                    }                   
                }
                if(ok == false)
                    throw new UndefinedTransitionException();
                else 
                    return true;
 
            }
            else
                return false;
        }
 
    }
 
    public String getCurrentStateName(){        
        return nomeDoEstadoAtual;
    }
 
    //metodo auxiliar, criado por mim mesmo;
    private void VerificarEstado(String[] strings){
        for(String st : strings){ 
            boolean ok = false;
            for( Estado estado  : estadoList ){
                if(st == estado.name){
                    ok = true;
                    break;
                }
            }
            if(ok == false)
                throw new UndefinedStateException();
        }
 
    }
}

Testes

ao aplicar a classe de teste solicitada pelo lab, verifiquei erro nos dois últimos métodos, ao se verificar o motivodos erros, percebi que estes não eram por causa de falhas no meu código e sim pela criação deste direcionada provavelmente pela solução do professor, então fiz pequenas alteraçoes em tais métodos de forma a serem coerentes coma miha aplicação (porque antes não era). O código de teste alterado encontra-se abaixo:

package stateMachine;
 
import junit.framework.TestCase;
 
public class TestStateMachine extends TestCase{
 
    public void testStateMachineSimple()
    {
        StateMachine sm = new StateMachine();
        sm.createState("feliz");
        sm.createState("triste");
        sm.setInitialState("triste");
        assertEquals("triste", sm.getCurrentStateName());
        sm.createEvent("jogar_bola", "triste", "feliz");
 
        try {
            sm.fireEvent("jogar_bola");
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
 
        assertEquals("feliz", sm.getCurrentStateName());
 
        try {
            sm.fireEvent("jogar_bola");
            fail("deve gerar excessão");
        }catch (UndefinedTransitionException e) {
        }
    }
 
    public void testArrayInFrom()
    {
        StateMachine sm = new StateMachine();
        sm.createState("feliz");
        sm.createState("triste");
        sm.createState("deprimido");
        sm.setInitialState("feliz");
        assertEquals("feliz", sm.getCurrentStateName());
        String[] from = {"triste", "feliz"};
        sm.createEvent("perder_mulher", from, "deprimido");
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
        assertEquals("deprimido", sm.getCurrentStateName());
        try {
            sm.fireEvent("perder_mulher");
            fail("deve gerar excessão");
        }catch (UndefinedTransitionException e) {
        }  
 
        sm = new StateMachine(); //outra maquina
        sm.createState("feliz");
        sm.createState("triste");
        sm.createState("deprimido");
        sm.setInitialState("triste");
        assertEquals("triste", sm.getCurrentStateName());
        String[] from2 = {"triste", "feliz"};
        sm.createEvent("perder_mulher", from2, "deprimido");
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
        assertEquals("deprimido", sm.getCurrentStateName());
        try {
            sm.fireEvent("perder_mulher");
            fail("deve gerar excessão");
        }catch (UndefinedTransitionException e) {
        }
    }
 
    public void testMultipleTransitions() {
        StateMachine sm = new StateMachine();
        sm.createState("super_feliz");
        sm.createState("feliz");
        sm.createState("triste");
        sm.createState("deprimido");
        sm.setInitialState("super_feliz");
 
        String[] from = {"triste", "feliz"};
        sm.createEvent("perder_mulher", from, "deprimido");
 
        sm.appendTransitionToEvent("perder_mulher", "super_feliz", "triste");
 
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
        assertEquals("triste", sm.getCurrentStateName());
 
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
        assertEquals("deprimido", sm.getCurrentStateName());
 
        try {
            sm.fireEvent("perder_mulher");
            fail("deve gerar excessão");
        }catch (UndefinedTransitionException e) {
        }
    }
 
    public void testStateCommand() {
        StateMachine sm = new StateMachine();
        sm.createState("super_feliz");
        sm.createState("feliz");
        sm.createState("triste");
        sm.createState("deprimido");
        sm.setInitialState("super_feliz");
 
        sm.createEvent("perder_mulher", "super_feliz", "triste", new EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Eu estou chorando!!! Não estou mais " + previousState +
                    ", agora estou " + this.machine.getCurrentStateName());
            }
        } );
 
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
        }
 
    }
 
    public void testEventCommand() {
        StateMachine sm = new StateMachine();
        boolean called = false;
        StateCommand avisar = new StateCommand(sm) {
            public void execute() {
                System.out.println("Agora eu fiquei " + this.machine.getCurrentStateName());
            }
        };
        sm.createState("super_feliz");
        sm.createState("feliz", avisar);
        sm.createState("triste", avisar);
        sm.createState("deprimido", avisar);
        sm.setInitialState("super_feliz");
        sm.createEvent("perder_mulher", "super_feliz", "triste");
        try {
            sm.fireEvent("perder_mulher");
        }catch (UndefinedTransitionException e) {
        }
    }
 
    public void testGuard() {
        temSuperPoderes = true;
        StateMachine sm = new StateMachine();
        sm.createState("super_feliz");
        sm.createState("feliz");
        sm.createState("triste");
        sm.createState("deprimido");
        sm.setInitialState("super_feliz");
        sm.createEvent("perder_mulher", "super_feliz", "triste");
        sm.setGuardToEvent("perder_mulher", new EventGuard() {
            public boolean shouldPerform() {
                return !temSuperPoderes;
            }
        });
        try {
            assertFalse(sm.fireEvent("perder_mulher"));
        }catch (UndefinedTransitionException e) {
        }
        temSuperPoderes = false;
        try {
            assertTrue(sm.fireEvent("perder_mulher"));
        }catch (UndefinedTransitionException e) {
            fail("não deve gerar excessão");
        }
    }
 
    private boolean temSuperPoderes = false;
 
    public boolean getTemSuperPoderes(){
        return temSuperPoderes;
    }
 
    public void testShouldReturnErrorIfEventDoesntExist() {
        StateMachine sm = new StateMachine();
        sm.createState("feliz");
        sm.createState("triste");
        sm.setInitialState("triste");
        assertEquals("triste", sm.getCurrentStateName());
        try{
            sm.createEvent("jogar_bola", "triste", "estado_nao_existe"); 
        }
        catch(UndefinedStateException e){ }
        try {
 
            sm.fireEvent("evento_nao_existente");
            fail("deve gerar excessão");
        }catch (UndefinedTransitionException e) {
        }catch (UndefinedEventException e) {
        }    
 
    }
 
    public void testShouldReturnErrorIfStateDoesntExist() {
        StateMachine sm = new StateMachine();
        sm.createState("feliz");
        sm.createState("triste");
        sm.setInitialState("triste");
        assertEquals("triste", sm.getCurrentStateName());         
 
        try { 
            sm.createEvent("jogar_bola", "triste", "estado_nao_existe");
            sm.fireEvent("jogar_bola");
            fail("deve gerar excessão");
        }
        catch(UndefinedStateException e){           
        }        
        catch(UndefinedTransitionException e) {
        } 
    }
}

No penúltimo método adicionei um novo try catch e no último coloquei o creatEvent dentro do try catch, sem alterar o propósito da testabilidade do programa, coo se pode observar dando uma analisada. Ao rodar o teste observamos a seguinte saída na imagem abaixo:

imagem1.jpg

Agora iremos testar uma situação dada como sugestão no lab. Para tal copiou-se o código dado na instrução do lab, colocando-os em um outro pacote, chamado de pacoteteste. Também nesse pacote tem um classe criado por mim mesmo para testar a maquina de estados, como pedido no lab. O código dessa classe está abaixo:

package pacoteTeste;
 
public class Professor extends WithSM implements stateMachine.Observer, TurmaObserver
{
 
    public Professor() {
        super();
        setUpSM();
        sm.registerObserver(this);
    }
 
    public String fazerProva() {
        if(getState() == "motivado")
            return "Prova com noção";
        else if(getState() == "desmotivado" || getState() == "vacinado")
            return "Prova ruim, correção carteada";
        else if(getState() == "irado")
            return "Prova sem noção, média D";
        else
            return "Prova que nunca será feita";
    }
 
    public String prepararAula() {
        if(getState() == "motivado")
            return "Aula preparada com carinho (5 horas)";
        else if(getState() == "desmotivado" || getState() == "vacinado")
            return "Aula preparada com pressa (1 hora)";
        else if(getState() == "irado")
            return "Aula jogada (5 min)";
        else
            return "Aula que nunca será feita";
    }
 
    private boolean ganhandoMilhoes = false;
    public boolean isGanhandoMilhoesDeDolares(){
        return ganhandoMilhoes;
    }
    public void setGanhandoMilhoesDeDolares(boolean is){
        ganhandoMilhoes = is;
    }
 
    public void update(stateMachine.StateMachine sm, String newStateName) {
        System.out.println("Meu estado agora é " + newStateName);
    }
 
    public void updateFromTurma(Turma t) {
        if (t.getState() == "prestando atenção" || t.getState() == "cancerizando nos labs")
            this.fire("motivar");
        else
            this.fire("desmotivar");
    }
 
    private void setUpSM() {
 
        //criando estados
        sm.createState("motivado");
        sm.createState("desmotivado");
        sm.createState("vacinado");
        sm.createState("irado", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("nunca mais serei o mesmo!");
            }
        } );
        sm.setInitialState("motivado");
 
        //evento desmotivar
        sm.createEvent("desmotivar", "motivado", "desmotivado", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Fiquei mais desmotivado!");
            }
        });
        String[] from = {"desmotivado", "irado", "vacinado"};
        sm.appendTransitionToEvent("desmotivar", from, "irado");
        sm.setGuardToEvent("desmotivar", new stateMachine.EventGuard() {
            public boolean shouldPerform(){
               return !isGanhandoMilhoesDeDolares();  //nunca fica desmotivado se ganha milhoes
            }
        });
 
        //evento motivar
        sm.createEvent("motivar", "desmotivado", "motivado", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Oba, fiquei mais motivado!");
            }
        });
        String[] from2 = {"irado", "vacinado"};
        sm.appendTransitionToEvent("motivar", from2, "vacinado");
        sm.appendTransitionToEvent("motivar", "motivado", "motivado");
 
    }
 
}

O diagram UML dos testes estão abaixo:

imagem2.jpg

Segue abaixo a execução do código feito por mim mesmo:

imagem3.jpg

Segue também a imagem de teste do código suregirido no lab. Cabe ressaltar que seu funcionamente está de acordo com o esperado.

imagem4.jpg

Conclusão

De modo geral, esse lab permitiu entender o funcionamento do método observer que encontrava-se meio confuso em mim. Pude perceber sua importancia e quando deve-se o utilizar. Aprendi o uso de classes anônimas e aprimorei um pouco mais a habilidade de programar em java. Quanto a proposta, achei ruim a forma como os métodos creatEvent e o append foram feitos, há muita duplicação de código lá, para tentar a amenizar, tive inclusive que criar um método auxiliar, no caso para testar a existência dos estados entrados no método. Achei o lab bastante extenso e trabalhoso, porém, apesar do árduo trabalho, proveitoso para ver o funcionamento do padrão observer. Acho que a melhor forma de aprendermos um padrão dado é assim, exercitando-os nos labs para sentir melhor o seu funcionamento, só com a teoria não basta, a prova disse é que me encontraba com algumas dúvidas em tais padrões, mesmo depois de reler muito os slides de aula.

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