Padrão de Estado
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 95 de 107.
O padrão State permite que um objeto altere seu comportamento quando seu estado interno muda, fazendo parecer que o objeto mudou de classe. Enquanto o Template Method controla as etapas do algoritmo, o State encapsula o comportamento específico de cada estado em objetos separados e delega ao estado atual.
Em Go, definimos uma interface de estado e criamos estados concretos que implementam comportamentos diferentes:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Cada estado determina o que acontece e qual estado vem a seguir:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}A mesma chamada de método produz resultados diferentes com base no estado atual:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Rascunho enviado para moderação
fmt.Println(doc.Publish()) // Moderação aprovada, agora publicado
fmt.Println(doc.Publish()) // Já publicadoState é ideal para objetos com modos de operação distintos, como fluxos de processamento de pedidos, componentes de UI ou manipuladores de conexão, nos quais o comportamento depende inteiramente do estado atual.
Desafio
FácilVamos criar um sistema de suporte de tickets usando o padrão State! Você criará um ticket de suporte que passa por diferentes etapas, desde a abertura, passando pelo atendimento, até a resolução, com cada estado determinando quais ações são possíveis e o que acontece em seguida.
Você organizará seu código em três arquivos:
state.go: defina sua interface de estado e os estados concretos que representam cada etapa do ciclo de vida de um ticket.Crie uma interface
TicketStatecom um métodoHandle(t *Ticket) stringque processe o ticket e potencialmente faça a transição para o próximo estado.Implemente três estados:
OpenState: quando processado, faz a transição do ticket paraInProgressStatee retornaTicket opened, assigning to support teamInProgressState: quando processado, faz a transição paraResolvedStatee retornaWorking on ticket, issue resolvedResolvedState: quando processado, permanece no mesmo estado e retornaTicket already resolved
ticket.go: crie sua struct de ticket, que mantém o estado atual e delega o comportamento a ele.Crie uma struct
Ticketcom um campoID(string) e um campostate(TicketState). Adicione estes métodos:SetState(s TicketState)para alterar o estado atual do ticketProcess() stringque delega ao método Handle do estado atual
Crie um construtor
NewTicket(id string) *Ticketque retorne um ticket iniciado no estadoOpenState.main.go: demonstre como a mesma ação produz resultados diferentes com base no estado atual do ticket.Leia um ID de ticket e o número de vezes que o ticket deve ser processado. Crie um novo ticket com esse ID e, em seguida, chame
Process()o número de vezes especificado, imprimindo cada resultado em uma linha separada.
As seguintes entradas serão fornecidas:
- Linha 1: ID do ticket
- Linha 2: número de vezes que o ticket deve ser processado
Por exemplo, dada a entrada:
TKT-001
3Sua saída deve ser:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedE dada a entrada:
TKT-500
5Sua saída deve ser:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedE dada a entrada:
ISSUE-42
1Sua saída deve ser:
Ticket opened, assigning to support teamObserve como chamar Process() no mesmo ticket produz resultados diferentes a cada vez: o comportamento do ticket muda à medida que ele passa pelos estados. Quando é resolvido, ele permanece resolvido, independentemente de quantas vezes você o processe. O objeto ticket parece mudar seu comportamento, mas na verdade está delegando a diferentes objetos de estado!
Experimente você mesmo
package main
import (
"fmt"
)
func main() {
// Ler entrada
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: Criar um novo ticket com o ID fornecido
// TODO: Processar o ticket o número especificado de vezes
// e imprimir cada resultado em uma linha separada
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos11Biblioteca Padrão e POO
io.Reader e io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRevisão - Modelos de API REST14Padrões de Projeto — Parte 2
Padrão de ComandoPadrão AdapterPadrão DecoratorPadrão de Método TemplatePadrão de EstadoPadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online