Padrão State
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 com que pareça que o objeto mudou de classe. Enquanto o Template Method controla as etapas do algoritmo, o State encapsula o comportamento específico do estado em objetos separados e delega para o 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á publicadoO State é ideal para objetos com modos de operação distintos, como fluxos de trabalho de processamento de pedidos, componentes de UI ou manipuladores de conexão onde o comportamento depende inteiramente do estado atual.
Desafio
FácilVamos construir um sistema de suporte de tickets usando o padrão State! Você criará um ticket de suporte que passa por diferentes estágios—desde ser aberto, até ser trabalhado, até ser resolvido—com cada estado determinando quais ações são possíveis e o que acontece a seguir.
Você organizará seu código em três arquivos:
state.go: Defina sua interface de estado e os estados concretos que representam cada estágio do ciclo de vida de um ticket.Crie uma interface
TicketStatecom um métodoHandle(t *Ticket) stringque processa o ticket e potencialmente o transiciona para o próximo estado.Implemente três estados:
OpenState— quando manipulado, transiciona o ticket paraInProgressStatee retornaTicket opened, assigning to support teamInProgressState— quando manipulado, transiciona paraResolvedStatee retornaWorking on ticket, issue resolvedResolvedState— quando manipulado, 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.Construa 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 para o método Handle do estado atual
Crie um construtor
NewTicket(id string) *Ticketque retorna um ticket começando noOpenState.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 para processar o ticket. Crie um novo ticket com esse ID e, em seguida, chame
Process()o número especificado de vezes, imprimindo cada resultado em uma linha separada.
As seguintes entradas serão fornecidas:
- Linha 1: ID do Ticket
- Linha 2: Número de vezes para processar o ticket
Por exemplo, dado:
TKT-001
3Sua saída deve ser:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedE dado:
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 dado:
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 conforme ele se move pelos estados. Uma vez resolvido, ele permanece resolvido, não importa quantas vezes você o processe. O objeto ticket parece mudar seu comportamento, mas na verdade ele está delegando para diferentes objetos de estado!
Folha de consulta
O padrão State permite que um objeto altere seu comportamento quando seu estado interno muda. O objeto delega o comportamento para objetos de estado separados que representam estados diferentes.
Defina uma interface de estado:
type State interface {
Handle(d *Document) string
}Crie um objeto de contexto que mantém o estado atual:
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Implemente estados concretos que encapsulam o comportamento específico do estado:
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"
}Cada estado determina o que acontece e faz a transição para o próximo estado:
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á publicadoO padrão State é ideal para objetos com modos distintos de operação, onde o comportamento depende inteiramente do estado atual.
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 ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser11Biblioteca Padrão & POO
io.Reader & io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRecapitulação - Modelos de API REST14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool