Padrão State
Last updated
Last updated
O padrão State é intimamente relacionado com o conceito de uma Máquina de Estado Finito. Ou ainda, se você já estou Diagrama de Estados em UML, vai ser mais fácil de entender como esse padrão funciona.
De qualquer maneira, esse padrão se foca nos problemas relacionados a troca de estados de um objeto. Esse é um problema bastante comum. Busque em sua memória e provavelmente você irá lembrar de alguma situação onde você verificou o estado de um objeto qualquer para decidir como um algoritmo deve ser executado.
Vamos tomar como exemplo aqui um personagem de jogo, como Mario (do Super Mario World de Super Nintendo). Durante o jogo acontecem várias trocas de estado com Mario. Ao pegar uma flor de fogo, por exemplo, o mario cresce (caso esteja pequeno), e fica com a habilidade de soltar bolas de fogo.
Desenvolvendo um pouco mais o pensamento temos um grande conjunto de possíveis estados, e cada transição depende de qual é o estado atual do personagem. Como falado anteriormente, ao pegar uma flor de fogo podem acontecer quatro ações diferentes, dependendo de qual o estado atual do mario:
Se Mario estiver pequeno → Mario fica grande e com poder de fogo
Se Mario estiver grande → Mario fica com poder de fogo
Se Mario estiver com poder de fogo → Mario ganha 1000 pontos
Se Mario estiver com capa → Mario fica com poder de fogo
Todas estas condições devem ser checadas para realizar esta única troca de estado. Agora imagine os vários estados que são possíveis no jogo e a complexidade para realizar a troca destes estados: Mario pequeno, Mario grande, Mario flor e Mario pena. Um código semelhante ao mostrado abaixo provavelmente seria criado a princípio:
Com certeza não vale a pena investir tempo e código numa solução que utilize várias verificações para cada troca de estado. Para não correr o risco de esquecer de tratar algum estado e deixar o código bem mais fácil de manter, vamos usar a proposta do Padrão State para resolver o problema e eliminar todas as condicionais desse exemplo.
No livro “Design Patterns: Elements of Reusable Object-Oriented Software”, a intenção declarada do padrão é:
Permite a um objeto alterar seu comportamento quando o seu estado interno muda. Oobjeto parecerá ter mudado sua classe.
Esse padrão vai alterar o comportamento de um objeto quando houver alguma mudança no seu estado interno (atributo estadoAtual
no exemplo anterior), como se ele tivesse mudado de classe.
Para implementar a idéia do padrão será necessário criar uma interface básica para todos os estados, contendo os métodos que podem alterar o estado do personagem. Como definimos anteriormente o que pode causar alteração nos estados do objeto Mario, estas serão as operações básicas que vão fazer parte da interface. O que era o enum
MarioState
antes, agora vai ser refatorado para uma interface.
Os retornos dos métodos devem ser o mesmo tipo da interface, o porquê disso ficará mais óbvio a frente, mas por hora entenda que ao executar um dos métodos acima, devemos informar qual é o novo estado. Vamos implementar um dos exemplos a partir dessa interface:
Nesse novo formato, os estados ficam encapsulados em objetos distintos e cada classe se preocupa apenas com as funcionalidades que serão executadas quando Mario estiver naquele determinado estado. Vejamos um exemplo da classe que vai usar os objetos de estado
Cada novo estado que aparecer também não vai precisar alterar a classe que usa os estados, apenas implementar a interface e retornar os estados em que ela pode transitar. A classe Mario
possui uma referência para um objeto estado (estadoAtual
), este estado vai ser atualizado de acordo com as operações de troca de estados, definidas logo em seguida. Quando uma operação for invocada, o objeto estado vai executar a operação e se atualizará automaticamente.
Dessa maneira, uma execução como a mostrada abaixo dá a impressão de que o objeto mudou de estado interno, deixando transparente como isso acontece.
Preste atenção no diagrama de classes abaixo, mostrando de maneira visual a organização desse padrão. De uma maneira geral, o que ele faz é usar composição para que a responsabilidade de cada uma das ações seja extraída do Contexto
e delegada a objetos de Estado
, que são implementados nos EstadosConcretos
, que podem surgir a medida que o projeto precisa crescer.
O exemplo mostrado aqui pode ser encontrado nesse repositório. Os estados não estão implementados em classes separadas, mas sim em um enum
a parte, explore o uso desse recurso da linguagem Java e pondere sobre suas vantagens e desvantagens. Além disso, verifique se isso também é possível em outras linguagens orientadas a objeto, a medida que desenvolve o artigo.
O exemplo de problema e solução foi extraído do site do Marcos Brizeno. Algumas adaptações foram feitas para facilitar a explicação pro nosso contexto.
A ilustração no começo dessa página é propriedade da Nintendo