Um padrão de projeto é um modelo que resolve um problema comumente recorrente no projeto de software.
O padrão de estado é um padrão de comportamento que permite que um objeto altere seu comportamento quando seu estado interno muda.
Aqui você aprenderá como usar o padrão de estado no TypeScript.
Qual é o padrão de estado?
O padrão de projeto de estado está intimamente relacionado a uma máquina de estado finito, que descreve um programa que existe em um finito número de estados em um determinado momento e se comporta de maneira diferente dentro de cada estado.
Existem regras limitadas e predeterminadas - transições - que governam os outros estados para os quais cada estado pode mudar.
Para contextualizar, em uma loja online, se o pedido de compras de um cliente foi “entregue”, ele não pode ser “cancelado” porque já foi “entregue”. “Entregue” e “Cancelado” são estados finitos do pedido, e o pedido se comportará de maneira diferente com base em seu estado.
O padrão de estado cria uma classe para cada estado possível, com comportamento específico do estado contido em cada classe.
Um exemplo de aplicativo baseado em estado
Por exemplo, suponha que você esteja criando um aplicativo que rastreia os estados de um artigo para uma editora. Um artigo pode estar pendente de aprovação, redigido por um escritor, editado por um editor ou publicado. Esses são os estados finitos de um artigo a ser publicado; dentro de cada estado único, o artigo se comporta de maneira diferente.
Você pode visualizar os diferentes estados e transições do aplicativo de artigo com o diagrama de estado abaixo:
Implementando este cenário no código, primeiro você teria que declarar uma interface para o artigo:
interfaceArtigoInterface{
tom(): vazio;
rascunho(): vazio;
editar(): vazio;
publicar(): vazio;
}
Esta interface terá todos os estados possíveis da aplicação.
Em seguida, crie um aplicativo que implemente todos os métodos de interface:
// Aplicativo
aulaArtigoimplementosArtigoInterface{
construtor() {
esse.showCurrentState();
}privadoshowCurrentState(): vazio{
//...
}públicotom(): vazio{
//...
}públicorascunho(): vazio{
//...
}públicoeditar(): vazio{
//...
}
públicopublicar(): vazio{
//...
}
}
O privado showCurrentState é um método utilitário. Este tutorial o utiliza para mostrar o que acontece em cada estado. Não é uma parte obrigatória do padrão de estado.
Lidando com Transições de Estado
Em seguida, você precisará lidar com as transições de estado. Lidar com a transição de estado em sua classe de aplicativo exigiria muitos declarações condicionais. Isso resultaria em código repetitivo que é mais difícil de ler e manter. Para resolver esse problema, você pode delegar a lógica de transição de cada estado para sua própria classe.
Antes de escrever cada classe de estado, você deve criar uma classe base abstrata para garantir que qualquer método chamado em um estado inválido gere um erro.
Por exemplo:
abstratoaulaArtigoEstadoimplementosArtigoInterface{
pitch(): ArticleState {
lançarnovoErro("Operação inválida: não é possível executar a tarefa em Estado atual");
}draft(): ArticleState {
lançarnovoErro("Operação inválida: não é possível executar a tarefa em Estado atual");
}edit(): ArticleState {
lançarnovoErro("Operação inválida: não é possível executar a tarefa em Estado atual");
}
publish(): ArticleState {
lançarnovoErro("Operação inválida: não é possível executar a tarefa em Estado atual");
}
}
Na classe base acima, todo método gera um erro. Agora, você deve sobrescrever cada método criando classes específicas que estende a classe base para cada estado. Cada classe específica conterá a lógica específica do estado.
Cada aplicativo tem um estado ocioso, que inicializa o aplicativo. O estado ocioso para este aplicativo definirá o aplicativo para o rascunho estado.
Por exemplo:
aulaPendenteDraftStateestendeArtigoEstado{
pitch(): ArticleState {
retornarnovo DraftState();
}
}
O tom método na classe acima inicializa o aplicativo definindo o estado atual para DraftState.
Em seguida, substitua o restante dos métodos da seguinte forma:
aulaDraftStateestendeArtigoEstado{
draft(): ArticleState {
retornarnovo EditingState();
}
}
Este código substitui o rascunho método e retorna uma instância do EditingState.
aulaEditingStateestendeArtigoEstado{
edit(): ArticleState {
retornarnovo EstadoPublicado();
}
}
O bloco de código acima substitui o editar método e retorna uma instância de Estado Publicado.
aulaEstado PublicadoestendeArtigoEstado{
publish(): ArticleState {
retornarnovo PendenteDraftState();
}
}
O bloco de código acima substitui o publicar método e coloca o aplicativo de volta em seu estado ocioso, PendenteDraftState.
Em seguida, você precisa permitir que o aplicativo altere seu estado internamente, referenciando o estado atual por meio de uma variável privada. Você pode fazer isso inicializando o estado ocioso dentro de sua classe de aplicativo e armazenando o valor em uma variável privada:
privado estado: ArtigoEstado = novo PendenteDraftState();
A seguir, atualize o showCurrentState método para imprimir o valor do estado atual:
privadoshowCurrentState(): vazio{
console.registro(esse.estado);
}
O showCurrentState O método registra o estado atual do aplicativo no console.
Por fim, reatribua a variável privada à instância do estado atual em cada um dos métodos de seu aplicativo.
Por exemplo, atualize seus aplicativos tom método para o bloco de código abaixo:
públicotom(): vazio{
esse.estado = esse.state.pitch();
esse.showCurrentState();
}
No bloco de código acima, o tom O método altera o estado do estado atual para o estado da afinação.
Da mesma forma, todos os outros métodos mudarão o estado do aplicativo atual para seus respectivos estados.
Atualize seus métodos de aplicativo para os blocos de código abaixo:
O rascunho método:
públicorascunho(): vazio{
esse.estado = esse.state.rascunho();
esse.showCurrentState();
}
O editar método:
públicoeditar(): vazio{
esse.estado = esse.state.edit();
esse.showCurrentState();
}
E a publicar método:
públicopublicar(): vazio{
esse.estado = esse.state.publish();
esse.showCurrentState();
}
Usando o aplicativo finalizado
Sua classe de aplicativo finalizada deve ser semelhante ao bloco de código abaixo:
// Aplicativo
aulaArtigoimplementosArtigoInterface{
privado estado: ArtigoEstado = novo PendenteDraftState();construtor() {
esse.showCurrentState();
}privadoshowCurrentState(): vazio{
console.registro(esse.estado);
}públicotom(): vazio{
esse.estado = esse.state.pitch();
esse.showCurrentState();
}públicorascunho(): vazio{
esse.estado = esse.state.rascunho();
esse.showCurrentState();
}públicoeditar(): vazio{
esse.estado = esse.state.edit();
esse.showCurrentState();
}
públicopublicar(): vazio{
esse.estado = esse.state.publish();
esse.showCurrentState();
}
}
Você pode testar as transições de estado chamando os métodos na sequência correta. Por exemplo:
const documentos = novo Artigo(); // PendingDraftState: {}
docs.pitch(); // DraftState: {}
docs.rascunho(); // EditingState: {}
docs.edit(); // Estado publicado: {}
docs.publish(); // PendingDraftState: {}
O bloco de código acima funciona porque os estados do aplicativo fizeram a transição apropriadamente.
Se você tentar mudar o estado de uma forma que não é permitida, por exemplo, do estado de pitch para o estado de edição, o aplicativo lançará um erro:
const documentos = novo Artigo(); // PendingDraftState: {}
docs.pitch() // DraftState: {}
docs.edit() // Operação inválida: não é possível executar a tarefa no estado atual
Você só deve usar esse padrão quando:
- Você está criando um objeto que se comporta de maneira diferente dependendo de seu estado atual.
- O objeto tem muitos estados.
- O comportamento específico do estado muda com frequência.
Vantagens e compensações do padrão de estado
Esse padrão elimina declarações condicionais volumosas e mantém a responsabilidade única e os princípios aberto/fechado. Mas pode ser um exagero se o aplicativo tiver poucos estados ou se seus estados não forem particularmente dinâmicos.