Lab3 Anderson Aiziro

aluno: Anderson Hoshiko Aiziro
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 13/10/2008 (2)

Introdução

Este laboratório possuiu duas partes principais: o desenvolvimento de uma biblioteca de máquina de estados e o desenvolvimento de uma máquina de estados para testar a biblioteca. O desenvolvimento da máquina de estados mostrou aos alunos como aplicar o padrão Observer, as vantagens de se utilizar classes anônimas e o uso do padrão Command. Com o desenvolvimento de uma máquina de testes, testamos as diversas funcionalidades da máquina de estados criada, assim como pudemos perceber a grande aplicabilidade da máquina de estados desenvolvida.

Desenvolvimento

Vamos começar pela biblioteca da máquina de estados. A dificuldade inicial foi entender como funcionava a máquina de estados e como tal seria implementada. Começando a partir da interface de StateMachine fornecida nas instruções do laboratório, foi possível obter as classes que constituíam a biblioteca. Assim, além da classe StateMachine, teremos as classes State, Transition e Event.
Utilizando agora o padrão Command, temos duas classes abstratas: o StateCommand e o EventCommand. Além disso, teremos 3 classes de erro: uma para transições(UndefinedTransitionException), uma para estados(UndefinedStateException) e uma para eventos(UndefinedEventException).
Vale notar que, com exceção de StateMachine, as classes abstratas EventCommand e StateCommand, e a interface EventGuard, todas as outras classes são protected, ou seja, não são acessadas diretamente de fora do pacote. Isto aumenta o encapsulamento e também garante maior segurança à nossa biblioteca, pois o usuário não conhecerá os detalhes de funcionamento das classes.


PADRÃO OBSERVER

A classe StateMachine é uma classe Observable, ou seja, objetos que implementarem a interface Observer receberão notificações sobre a mudança de estados de StateMachine. A interface Observer é a seguinte:

public interface Observer
{
    void update(StateMachine sm, String newStateName);  
}

E os métodos em StateMachine utilizados para adicionar observadores e notificá-los sobre alterações de estados é o seguinte:

public void registerObserver(Observer observer) {
        observers.add(observer);
}
 
public void notifyObservers(){
        for(Observer obs : observers ){
            obs.update(this, this.getCurrentStateName());
        }
}

PADRÃO COMMAND

Além do padrão Observer, neste laboratório, utilizamos o padrão Command, em que objetos são utilizados para representar ações. O padrão Command foi utilizado para determinarmos comandos em State e em Event. Quando o atributo StateCommand e/ou EventCommand eram diferentes de null para algum estado e/ou evento, realizávamos o método execute() definido em StateCommand e EventCommand, respectivamente. As classes abstratas StateCommand e EventCommand seguem abaixo:

public abstract class StateCommand
{
protected StateMachine machine;
    public StateCommand(StateMachine machine)
    {
        this.machine = machine;
    }
    public abstract void execute();
}
public abstract class EventCommand
{
  protected StateMachine machine;
    public EventCommand(StateMachine machine)
    {
        this.machine = machine;
    }
    public abstract void execute(String previousState);
}

STATE MACHINE

Agora, vamos desenvolver a classe StateMachine. A classe StateMachine é a principal e é a responsável pela interação com os observadores. Deste modo, é na classe StateMachine que armazenamos, em vetores, os possíveis estados da máquina, os eventos existentes entre os estados da máquina e as transições entre os estados da máquina. Além disso, armazenamos um vetor com os observadores para saber quem devemos notificar quando tivermos uma alteração.

Os métodos createState são responsáveis por adicionar estados ao nosso vetor de estados. Podemos criar estados de dois modos(overloading de métodos). Em um deles, somente passamos como parâmetro o nome do estado. No outro, utilizamos o conceito de classe anônima, definindo uma herança em um local determinado. Observemos que, é possível criar infinitos estados com o mesmo nome, e isto pode se tornar indesejável caso a aplicação seja muito grande. Para contornar este problema, deveríamos inicialmente checar se tal estado já existe para que não incluirmos o mesmo estado mais de uma vez. Contudo, em nossa aplicação, como o estado é unicamente definido pelo nome do estado, não teremos problemas mesmo com mais de um estado com o mesmo nome.

Os métodos createEvent relacionam estados, ou seja, criam arestas que ligam os estados na nossa máquina de estados. Cada aresta possui um nome e liga um estado de origem a um estado de destino, similar a um grafo. Assim, para sabermos se podemos realizar uma determinada transição, devemos saber o nome do evento e o vértice de origem. Desse modo, conseguimos encontrar o estado de destino. Aqui, podemos "quebrar" a máquina de estados. Do modo em que foi implementada, é possível criar dois eventos de mesmo nome, com o mesmo vértice de origem e estados de destino diferentes. Seria necessário checar as transições já criadas antes de criar uma nova transição.

Os métodos appendTransition adicionam novas arestas ao grafo, ou seja, criam novos eventos entre estados.

O método getCurrentStateName retorna o nome do estado atual em que nos encontramos.

O método fireEvent é responsável por verificar se o evento passado como parâmetro é "atirável", ou seja, se a partir do nosso estado atual, existe uma aresta com o nome passado como parâmetro do método para um estado final.
Após uma transição bem sucedida da máquina, passando-se o estado anterior da mesma como parâmetro, chamamos o método execute de EventCommand através do método currentevent.fireEventCommand(previous);
Após a máquina entrar em determinado estado, chamamos o método execute de StateCommand através do método currentstate.fireStateCommand();

As implementações da StateMachine seguem abaixo:

import java.util.*;
import java.io.*;
public class StateMachine
{
 
    private Vector<Observer> observers = new Vector<Observer>();
    private Vector<State> states = new Vector<State>();
    private Vector<Event> events = new Vector<Event>();
    private Vector<Transition> transitions = new Vector<Transition>();
    private State currentstate;
 
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
 
    public void notifyObservers(){
        for(Observer obs : observers ){
            obs.update(this, this.getCurrentStateName());
        }
    }
//métodos para a definição dos estados da máquina
    public void createState(String name, StateCommand onEnter){
        State adiciona= new State(this,name,onEnter);
        states.add(adiciona);
    }
    public void createState(String name){
        State adiciona= new State(this,name);
        states.add(adiciona);
    }
    public void setInitialState(String name) {
        try{
            currentstate=this.findState(name);
        }catch(UndefinedStateException e){}
    }
//métodos para a criação de eventos e transições
    public void createEvent(String name, String[] from, String to, EventCommand onSuccess){
        for(String origem : from){
            Event adiciona = new Event(this, name, origem, to,onSuccess);
            events.add(adiciona);
            appendTransitionToEvent(name,origem,to);
        }
    }    
 
    public void createEvent(String name, String[] from, String to){
        for(String origem : from){
            Event adiciona = new Event(this, name, origem, to);
            events.add(adiciona);
            appendTransitionToEvent(name,origem,to);
        }
    }
    public void createEvent(String name, String from, String to,  EventCommand onSuccess){
        Event adiciona = new Event(this,name,from,to,onSuccess);
        events.add(adiciona);     
        appendTransitionToEvent(name,from,to);
    }
    public void createEvent(String name, String from, String to){
        Event adiciona = new Event(this,name,from,to);
        events.add(adiciona);
        appendTransitionToEvent(name,from,to);
    }
 
    public void appendTransitionToEvent(String event, String fromState, String toState){
        Transition adiciona = new Transition(this,event,fromState,toState);
        transitions.add(adiciona);
    }
    public void appendTransitionToEvent(String event, String[] fromState, String toState){
        Transition adiciona;
        for(String from : fromState){
            adiciona = new Transition(this,event,from,toState);
            transitions.add(adiciona);
        }         
    }
public void setGuardToEvent(String event,EventGuard g){
        for(Event evento : events){
            if(event.equals(evento.getName()))
                evento.guards(g);
        }
    }
 
   // métodos para a execução da máquina
    public boolean fireEvent(String name) throws UndefinedTransitionException,UndefinedEventException,UndefinedStateException{
        boolean flag;
        flag=false;
        Event currentevent=null;
 
        //inicialmente, procuramos por um erro de evento, ou seja, verificamos se o evento que queremos realizar existe
        for(Event find : events )
            if(name.equals(find.getName())){
                currentevent=find;
                flag = true;
            }
        if(!flag){    //caso não exista, lançamos uma exceção
            throw new UndefinedEventException();
        }
            //em seguida, verificamos se do estado em que nos encontramos existe a transição que desejamos realizar
        if(findTransition(this.currentstate.getName(),name)==null){
            throw new UndefinedTransitionException();     //caso não exista, lançamos uma exceção
        }
 
        if(currentevent.isGuarded()){        //se a transição estiver com uma condição de guarda, não a realizamos
            return false;        //retornamos falso
        }
            //vamos agora mudar de estado
        String previous = currentstate.getName();    //vamos armazenar o estado de origem como o estado anterior
        //realizamos a transição e movemos para um outro estado. Nesta transição, utilizando a função findState, que lança uma exceção caso o estado para que estamos transitando não esteja definido, otemos o estado atual
        currentstate = findState(findTransition(this.currentstate.getName(),name));        
        //em seguida, lançamos as ações de evento e de estado, se estas estiverem definidas para o estado e evento que realizamos
        currentevent.fireEventCommand(previous);
        currentstate.fireStateCommand();
        return true;
    }
        //função auxiliar que retorna o nome do estado de destino de uma determinada transição com determinada origem
    private String findTransition(String origem, String evento) {
        for(Transition find: transitions){
            if(origem.equals(find.getOrigem()) && evento.equals(find.getEvent())){
                return find.getDestino();
            }
        }
        return null;
    }
        //retorna o nome do estado atual
    public String getCurrentStateName(){
        return this.currentstate.getName();
    }
        //função auxiliar que verifica se o estado está definido e, se não estiver, lança uma exceção
    private State findState(String name) throws UndefinedStateException {
        int i,size;
        for(State st: states){
            if(name.equals(st.getName())){
                return st;
            }
        }
        throw new UndefinedStateException();
    }
 
}

STATE

A classe State armazena os estados que já adicionamos à esta máquina de estados. Esta classe possui 2 construtores, aplicando o conceito de overloading e utilizando classes anônimas em 1 deles. Cada estado é definido por um nome e pode ter um atributo do tipo StateCommand, que é lançado pelo método fireStateCommand, sendo responsável por chamar o método execute() no momento em que chegamos ao estado com o atributo StateCommand.
A classe State e sua implementação estão apresentadas a seguir:

public class State
{
    private StateMachine sm;
    private String name;
    private StateCommand onEnter;
 
    State(StateMachine sm, String name,StateCommand onEnter) {
        this.name=name;
        this.sm=sm;
        this.onEnter=onEnter;
    }
 
    State(StateMachine sm, String name) {
        this.name=name;
        this.sm=sm;
    this.onEnter=null;
    }
 
    protected String getName() {
        return this.name;   
    }
    void fireStateCommand(){
        if(onEnter!=null)
            this.onEnter.execute();
    }    
}

EVENT

A classe Event armazena o nome das transições entre um estado de origem e um estado de destino. Esta classe possui 4 construtores, aplicando o conceito de overloading e utilizando classes anônimas em 2 deles. Cada evento é definido por um nome e pelos vértices de origem e destino e pode ter um atributo do tipo EventCommand, que é lançado pelo método fireEventCommand, sendo responsável por chamar o método execute() no momento em que o evento com o atributo StateCommand é lançado.
A classe Event e sua implementação estão apresentadas a seguir:

import java.util.Vector;
 
public class Event
{
private StateMachine sm;
    private String name;
    private String from;
    private String to;
    private EventCommand onSuccess;
    private Vector<EventGuard> guardeds = new Vector<EventGuard>();
 
    protected Event(StateMachine sm, String name, String from, String to, EventCommand onSuccess) {
        this.sm = sm;
        this.name = name;
        this.from = from;
        this.to = to;
        this.onSuccess = onSuccess;
    }
    protected Event(StateMachine sm, String name, String from, String to) {
        this(sm,name,from,to,null);
    }
 
    protected String getName(){
        return this.name;
    }
 
    protected void guards(EventGuard g){
        guardeds.add(g);
    }
 
    boolean isGuarded() {
        boolean ok=false;
        for(EventGuard eg: guardeds){
            if(!eg.shouldPerform()){
                ok=true;
            }
        }
        return ok;
    }
 
    void fireEventCommand(String previousState){
        if(onSuccess!=null)
            this.onSuccess.execute(previousState);
    }
 }

TRANSITION

A classe Transition armazena o nome das transições entre um estado de origem e um estado de destino. Esta classe possui apenas 1 construtor. Cada transição é definido pelo nome do evento e pelos estados de origem e destino.
A classe Transition e sua implementação estão apresentadas a seguir:

public class Transition
{
private StateMachine sm;
    private String event;
    private String from;
    private String to;
 
    protected Transition(StateMachine sm, String event, String from, String to) {
        this.sm = sm;
        this.event = event;
        this.from = from;
        this.to = to;
    }
 
    protected String getOrigem(){
        return this.from;
    }
    protected String getDestino(){
        return this.to;
    }
    protected String getEvent(){
        return this.event;
    }
}

EXCEÇÕES

Para garantir o bom funcionamento da biblioteca e evitar transições inexistentes, devemos criar classes que geram exceções quando tentamos realizar uma operação não permitida. Para isto, criaremos 3 classes: a UndefinedTransitionException, a UndefinedStateException e a UndefinedEventException, que serão lançadas quando se tentar executar um evento que não possui transição de saída para aquele evento no estado em que estivermos, quando tentamos transitar para um estado que não está definido na máquina e quando tentamos disparar um evento não definido na máquina respectivamente. As classes de exceção serão subclasses dos tipos Exception e RuntimeException.


DIAGRAMA DE CLASSES

O diagrama de classes da nossa biblioteca stateMachine é o seguinte:
diagrama1.JPG

Utilizando a classe de testes para verificar o funcionamento da nossa biblioteca, obtemos:
resultadotestes.JPG

Agora, vamos verificar a funcionalidade da stateMachine com o exemplo do professor. O diagrama de classes criado para o professor foi o seguinte:
diagramaprof.JPG

No diagrama acima, temos uma classe a mais, a classe testProfessor, criada para verificar a funcionalidade da stateMachine no exemplo do professor. Resultado dos testes:

O professor recebe milhoes (s/n):
n
Qual o comportamento da turma?
cancerizar
Oba, fiquei mais motivado!
Aula preparada com carinho (5 horas)
Prova com noção
Deseja continuar (s/n):
s
O professor recebe milhoes (s/n):
n
Qual o comportamento da turma?
dormir
Fiquei mais desmotivado!
Aula preparada com pressa (1 hora)
Prova ruim, correção carteada
Deseja continuar (s/n):
s
O professor recebe milhoes (s/n):
n
Qual o comportamento da turma?
dormir
Fiquei mais desmotivado!
nunca mais serei o mesmo!
Aula jogada (5 min)
Prova sem noção, média D
Deseja continuar (s/n):
s
O professor recebe milhoes (s/n):
n
Qual o comportamento da turma?
dormir
Fiquei mais desmotivado!
nunca mais serei o mesmo!
Aula jogada (5 min)
Prova sem noção, média D
Deseja continuar (s/n):
s
O professor recebe milhoes (s/n):
n
Qual o comportamento da turma?
cancerizar
Oba, fiquei mais motivado!
Aula preparada com pressa (1 hora)
Prova ruim, correção carteada
Deseja continuar (s/n):
n


EXEMPLO - CONCEITOS E COMPORTAMENTO DO ALUNO

O exemplo escolhido possui o seguinte diagrama de transições entre os estados:

diagramaaplicaçao.JPG

A partir do diagrama de estados acima, obtemos o seguinte diagrama de classes:

diagramaexemplo.JPG

O diagrama de classes e o funcionamento do exemplo acima é similar ao exemplo do professor. Teremos um aluno que possuirá um determinado comportamento, e este comportamento define o conceito do aluno nas matérias, de acordo com o diagrama de transições entre os estados acima.
Além disso, também implementamos uma condição de guarda que impede que o aluno piore seu conceito, ou seja, caso o aluno esteja safo na matéria, ele pode dormir sem prejudicar seu rendimento.
Pelos diagramas acima podemos perceber 3 comportamentos para o aluno: dormir, estudar e cancerizar.
Tais comportamentos são refletidos nas notas que ele recebe. Além disso, no diagrama de estados, fica implicito um contador, ou seja, a quantidade de vezes que o aluno pode possuir o comportamento dormir antes de ser desligado.
Se chegar ao comportamento desligado, o aluno não possui mais volta, ou seja, não consegue alterar o seu estado. E à medida que se aproxima do estado de desligamento, fica mais difícil de voltar aos conceitos.
Inicialmente, o aluno começa com o estado Summa. Cada vez que o aluno possui o comportamento dormir, ele piora seu conceito. Através dos comportamentos estudar e cancerizar, o aluno pode melhorar o seu conceito. Caso ele esteja safo, se ele dormir, o seu conceito não piora. O único estado sem saída é o estado Desligado.

As classes e interfaces do exemplo descrito acima são: Aluno, Conceitos, AlunoObserver e WithSM. Além disso, criamos uma classe Teste para comprovar o bom funcionamento da aplicação.
Os códigos das classes e interfaces são:

Classe Aluno:

package Maquina;
 
import java.util.Vector;
public class Aluno extends WithSM
{   
    private Vector<AlunoObserver> observers = new Vector<AlunoObserver>();
 
    public Aluno() {
         super();
         sm.createState("estudando");
         sm.createState("cancerizando");
         sm.createState("dormindo");
         sm.setInitialState("cancerizando");
 
         String from[] = {"estudando", "cancerizando", "dormindo" };
         sm.createEvent("dormir", from, "dormindo");
         sm.createEvent("cancerizar", from, "cancerizando");
         sm.createEvent("estudar", from, "estudando");
    }
 
    public void registerObserver(AlunoObserver observer){
        observers.add(observer);
    }
 
    private void notifyObservers() {
        for(AlunoObserver obs : observers) {
            obs.updateFromAluno(this);
        }
    }
 
    public void fire(String event) {
        super.fire(event);
        notifyObservers();
    }
}

Classe Conceitos:

package Maquina;
 
public class Conceitos extends WithSM implements stateMachine.Observer, AlunoObserver
{
 
    public Conceitos() {
        super();
        setUpSM();
        sm.registerObserver(this);
    }
 
    public String situacao() {
        if(getState().equals("SUMMA"))
            return "Conceito - Summa";
        else if(getState().equals("MediaMB") )
            return "Conceito - MB";
        else if(getState() == "MediaR/B")
            return "Conceito - R/B";
        else if(getState() == "MediaI/D")
            return "Conceito - I/D";
        else if(getState() == "Quase_desligando")
            return "Quase desligando";
        else return "Desligado";
 
    }
    private boolean safo = false;
    public boolean estaSafo(){
        return safo;
    }
    public void setSafo(boolean is){
        safo = is;
    }
 
    public void update(stateMachine.StateMachine sm, String newStateName) {
        System.out.println("Meu estado agora é " + newStateName);
    }
 
    public void updateFromAluno(Aluno t) {
        if (t.getState().equals("estudando"))
            this.fire("estudar");
        else if(t.getState().equals("cancerizando"))
            this.fire("cancerizar");
        else
            this.fire("dormir");
    }
 
    private void setUpSM() {
 
        //criando estados
        sm.createState("SUMMA");
        sm.createState("MediaMB");
        sm.createState("MediaR/B");
        sm.createState("MediaI/D",new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("A situação está perigosa, é bom estudar!");
            }
         });
 
        sm.createState("Quase_desligando",new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Precisa cancerizar urgentemente!!!");
            }
        });
 
        sm.createState("Desligado", new stateMachine.StateCommand(sm) {
            public void execute() {
                System.out.println("Agora não adianta! Foi desligado!");
            }
        } );
        sm.setInitialState("SUMMA");
 
        //evento dormir!
        sm.createEvent("dormir", "SUMMA", "MediaMB", new stateMachine.EventCommand(sm) {
            public void execute(String previousState) {
                System.out.println("Sua nota está caindo!");
            }
        });
        sm.appendTransitionToEvent("dormir", "MediaMB", "MediaR/B" );
        sm.appendTransitionToEvent("dormir", "MediaR/B","MediaI/D");
        sm.appendTransitionToEvent("dormir", "MediaI/D", "Quase_desligando");
 
        String[] from = {"Desligado", "Quase_desligando"};
        sm.appendTransitionToEvent("dormir",from,"Desligado");
 
        sm.setGuardToEvent("dormir", new stateMachine.EventGuard() {
            public boolean shouldPerform(){
               return !estaSafo();  
            }  
        });
 
        //evento estudar
        sm.createEvent("estudar", "SUMMA", "SUMMA");
        sm.appendTransitionToEvent("estudar", "MediaMB","MediaMB");
        sm.appendTransitionToEvent("estudar", "MediaR/B", "MediaMB" );
        sm.appendTransitionToEvent("estudar", "MediaI/D","MediaR/B");
        sm.appendTransitionToEvent("estudar", "Quase_desligando", "Quase_desligando");
        sm.appendTransitionToEvent("estudar", "Desligado", "Desligado");
 
        //evento cancerizar
        sm.createEvent("cancerizar", "Quase_desligando", "MediaI/D");
        String[] from2 = {"SUMMA", "MediaR/B","MediaMB"};
        sm.appendTransitionToEvent("cancerizar", from2, "SUMMA");
 
        sm.appendTransitionToEvent("cancerizar", "Desligado", "Desligado");
        sm.appendTransitionToEvent("cancerizar", "MediaI/D", "MediaMB");
 
    }
}

Classe WithSM

package Maquina;
 
import stateMachine.*;
 
public class WithSM
{
    protected StateMachine sm;
    public WithSM() {
         sm = new StateMachine();
    }
 
    public void fire(String eventName){
        try{
          sm.fireEvent(eventName);
        } catch(UndefinedTransitionException e) {
            System.out.println("ERRO: trasição não definida: " + e.getMessage());
        }
    }
 
    public String getState(){
        return sm.getCurrentStateName();
    }
}

Interface AlunoObserver

package Maquina;
 
public interface AlunoObserver
{
    void updateFromAluno(Aluno t);
}

Classe para testar a aplicação Teste

package Maquina;
 
import java.io.*;
import java.util.*;
public class Teste
{
    public static void main(String[] args){
          try{
            BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
            Scanner scan = new Scanner (System.in);
 
            Conceitos nota = new Conceitos();
            Aluno aluno = new Aluno();
            aluno.registerObserver(nota);
            String safo="s";
            while( safo.equals("s") ){   
                System.out.println("O aluno esta safo?");
                safo = is.readLine();
                if(safo.equals("s")){
                    System.out.println("O aluno pode dormir!\n");
                    nota.setSafo(true);
                }
                else nota.setSafo(false);
                System.out.println("O que ele ira fazer? ");
                safo = is.readLine();
                aluno.fire(safo);
                System.out.println( nota.situacao() );
                System.out.println("Continuar: ");
                safo = is.readLine();
            }
        }
        catch(IOException e){
             System.out.println("IOException"+e);
             return;
        }
    }
}

Testando a aplicação, obtivemos os seguintes resultados:
cancerizar
Conceito - MB
Continuar:
s
O aluno esta safo?
n
O que ele ira fazer?
dormir
Sua nota está caindo!
Conceito - R/B
Continuar:
s
O aluno esta safo?
n
O que ele ira fazer?
dormir
Sua nota está caindo!
A situação está perigosa, é bom estudar!
Conceito - I/D
Continuar:
s
O aluno esta safo?
n
O que ele ira fazer?
dormir
Sua nota está caindo!
Precisa cancerizar urgentemente!!!
Quase desligando
Continuar:
s
O aluno esta safo?
n
O que ele ira fazer?
dormir
Sua nota está caindo!
Agora não adianta! Foi desligado!
Desligado
Continuar:
s
O aluno esta safo?
n
O que ele ira fazer?
cancerizar
Agora não adianta! Foi desligado!
Desligado
Continuar:
n

Conclusão

Ao contrário dos outros dois laboratórios, este laboratório envolveu o modelamento da máquina de estados. Nos outros laboratórios, o modelo já estava dado, e acredito que este foi o principal motivo para este laboratório ser considerado mais difícil e exigir mais tempo dos alunos. Contudo, acredito que este laboratório foi muito mais interessante e instrutivo que os demais. A principal dificuldade foi dar o passo inicial, ou seja, sair da interface de StateMachine e entender como a biblioteca da máquina de estados funciona. Contudo, analisando cada um dos métodos de StateMachine e estudando o padrão Command, que não havíamos implementado antes, foi possível entender como as entidades se relacionavam.
Neste laboratório foi possível colocar em prática os padrões Observer e Command, aprender a utilizar classes anônimas e aplicar muitos conceitos de orientação a objetos, como por exemplo o Encapsulamento, restringindo o conhecimento dos detalhes de implementação por parte do usuário e garantindo maior segurança à nossa biblioteca. Além disso, outro conceito utilizado foi o de Overloading, nos métodos CreateEvent, CreateState e AppendTransitionToEvent.

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