aluno: Douglas Bokliang Ang Cunha
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 14/10/2008 (3)
Introdução
Neste laboratório desenvolvemos uma biblioteca de máquina de estados, aplicando esta em dois exemplos mostrados mais adiante. Para concluir este trabalho foi necessário modelar o problema usando Orientação Objeto, utilizar dois diferentes tipos de padrões (Observer e Command) e ainda aprender a utilziar classes anônimas.
Desenvolvimento
Para desenvolver a biblioteca de máquina de estados, foi utilizado as seguintes classes:
- StateMachine
Principal classe do pacote, onde fica definida a interface pública da máquina de estados. Entre outros, possui os métodos para criar um estado e criar um evento, métodos essenciais na aplicação da biblioteca para uma determinada máquina.
package stateMachine;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Vector;
public class StateMachine {
private State currentState;
private Dictionary<String, State> states = new Hashtable<String, State>();
private Dictionary<String, Event> events = new Hashtable<String, Event>();
private Vector<Observer> observers = new Vector<Observer>();
public void registerObserver(Observer observer){
observers.add(observer);
}
protected void notifyObservers(){
for(Observer obs : observers){
obs.update(this, currentState.name);
}
}
//métodos para a definição da máquina
public void createState(String name, StateCommand onEnter){
State s = new State(name);
s.command = onEnter;
states.put(name, s);
}
public void createState(String name){
createState(name, null);
}
public void setInitialState(String name){
currentState = states.get(name);
}
public void createEvent(String name, String[] from, String to, EventCommand onSuccess){
Event e = new Event(name, this);
for(String str : from){
e.from.add(str);
e.to.add(to);
}
e.command = onSuccess;
events.put(name,e);
}
public void createEvent(String name, String[] from, String to){
createEvent(name, from, to, null);
}
public void createEvent(String name, String from, String to, EventCommand onSuccess){
String[] array = new String[1];
array[0] = from;
createEvent(name, array, to, onSuccess);
}
public void createEvent(String name, String from, String to){
createEvent(name, from, to, null);
}
public void appendTransitionToEvent(String event, String fromState, String toState){
String[] array = new String[1];
array[0] = fromState;
appendTransitionToEvent(event, array, toState);
}
public void appendTransitionToEvent(String event, String[] fromState, String toState){
Event e = events.get(event);
for(int i = 0; i < fromState.length; i++){
e.from.add(fromState[i]);
e.to.add(toState);
}
}
public void setGuardToEvent(String event,EventGuard g){
Event e = events.get(event);
e.guard = g;
}
// métodos para a execução da máquina
public boolean fireEvent(String name) throws UndefinedTransitionException{
boolean fired = true;
Event e = events.get(name);
if (e == null) throw new UndefinedEventError("Undefined Event Error");
if(e.guard != null && !e.guard.shouldPerform())
fired = false;
else{
State s = Transition(name);
if(s == null) throw new UndefinedTransitionException("Undefined Transition Exception");
else{
String previousState = currentState.name;
currentState = s;
notifyObservers();
if(e.command != null)
e.command.execute(previousState);
if(currentState.command != null)
currentState.command.execute();
}
}
return fired;
}
public String getCurrentStateName(){
return currentState.name;
}
private State Transition(String event) {
Event e = events.get(event);
State newState = null;
for(String str : e.from){
if(str.equals(currentState.name)) {
String state = e.to.get(e.from.indexOf(str));
newState = states.get(state);
}
}
return newState;
}
}
-State e StateCommand
Na classe State fica definido os parâmetros que um estado deve ter, e a classe abstrata StateCommand é responsável por chamar o método execute(), o qual tem seu corpo definido através de classes anônimas.
-Event e EventCommand
A classe Event não apenas possui os parâmetros de um evento, mas também determina todas as transições de estados que tal evento pode realizar. Isto é feito através de dois Vectors, um representando os estados de saída e um representando os estados de entrada, sendo que quando os índices dos dois são iguais quer dizer que o evento pode levar do respectivo estado de saída para o respectivo estado de entrada.
A classe abstrata EventCommand tem a mesma função para os eventos que a StateCommand tem para os estados.
-EventGuard
Interface associada a classe Event. Quando há uma condição de guarda necessária para que um evento ocorra (condição implementada em setGuardToEvent() da classe StateMachine através de classes anônimas), esta é verificada através do método shouldPerform(), sendo que se este retornar um valor falso, o evento não ocorre.
-Observer
Interface responsável por atualizar o novo estado no qual a máquina se encontra para todos os observadores. Interface relacionada com o padrão Observer.
-UndefinedTransitionException
Quando um evento não está determinado para um certo estado atual, o método fireEvent() de StateMachine deve lançar esta exceção.
Obs: Apenas coloquei o código da classe StateMachine nesta parte do relatório por considerar a mais indispensável pro relatório, as demais podem ser vistas no arquivo em anexo.
Teste
Após terminar de implementar a biblioteca da máquina de estados, o teste fornecido nas intruções do laboratório foi executado. Eis o resultado:
Exemplo1: Professor e Turma (fornecido nas instruções)
Para testar o exemplo fornecido nas instruções, foi criado a classe NewMain a seguir para verificar se as transições estavam ocorrendo como o esperado. Veja o código desta classe abaixo:
package teste; public class NewMain { public static void main(String[] args) { Professor prof = new Professor(); Turma t = new Turma(); t.registerObserver(prof); t.fire("cancerizar"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("dormir"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("ser_atenta"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("usar_note"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("dormir"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("dormir"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("cancerizar"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("ser_atenta"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("cancerizar"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); prof.setGanhandoMilhoesDeDolares(true); t.fire("dormir"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); t.fire("usar_note"); System.out.println(prof.prepararAula()); System.out.println(prof.fazerProva()+"\n"); } }
A execução deste main gerou o seguinte output esperado:
Exemplo2: AlunoITA
Em seguida, foi feito uma outra aplicação da máquina de estados criada. A idéia deste exemplo foi fazer uma simulação simplificada do cotidiano de um aluno do ITA, no qual existem vários eventos, deixando o aluno transitar entre alguns estados. Veja o diagrama de estados:
Foi criado novamente uma classe para fazer a simulação do exemplo. Esta classe está especificada abaixo:
package exemplo; import stateMachine.*; public class NewMain { public static void main(String[] args) { AlunoITA a = new AlunoITA(); try{ a.fireEvent("acordar"); a.fireEvent("ir para aula"); a.fireEvent("ir para lab"); a.fireEvent("dormir"); a.fireEvent("acordar"); a.fireEvent("tomar red bull"); //nao deve fazer nada, nao tem prova amanha a.fireEvent("aula de CES22"); a.fireEvent("fazer prova"); a.fireEvent("terminar prova"); a.setTemProvaAmanha(true); a.fireEvent("dormir"); //nao deve dormir, tem prova amanha a.fireEvent("tomar red bull"); a.fireEvent("ir para aula"); a.fireEvent("fazer prova"); a.setTemProvaAmanha(false); a.fireEvent("terminar prova"); a.fireEvent("dormir"); }catch(UndefinedTransitionException e){ System.out.println("ERRO: trasição não definida: " + e.getMessage()); } } }
Após executar o main, obteve-se o seguinte resultado esperado:
Conclusão
Este laboratório se mostrou muito complicado no início. Demorei diversas horas para começar a entender o que estava acontecendo, o que cada método deveria fazer, como implementar etc. No entanto, depois de ler várias vezes as intruções do lab, estudar o padrão Command no Design Patterns e pedir um pouco de ajuda com os amigos, o trabalho começou a sair e se mostrou não ser tão complicado quanto parecia.
No fim das contas, acredito que este lab foi importante principalmente por ter sido o primeiro no qual a gente precisou se virar mais por conta própria, teve menos informações fornecidas em relação aos outros. Além disso, foi possível utilizar dois padrões de projetos e aprender sobre classes anônimas.









