Patrón State
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 95 de 107.
El patrón State permite que un objeto cambie su comportamiento cuando cambia su estado interno, haciendo que parezca como si el objeto hubiera cambiado de clase. Mientras que Template Method controla los pasos de un algoritmo, State encapsula el comportamiento específico del estado en objetos separados y delega al estado actual.
En Go, definimos una interfaz de estado y creamos estados concretos que implementan diferentes comportamientos:
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 qué sucede y qué estado viene a continuación:
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"
}La misma llamada al método produce resultados diferentes según el estado actual:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Borrador enviado para moderación
fmt.Println(doc.Publish()) // Moderación aprobada, ahora publicado
fmt.Println(doc.Publish()) // Ya publicadoState es ideal para objetos con distintos modos de operación, como flujos de trabajo de procesamiento de pedidos, componentes de UI o controladores de conexión donde el comportamiento depende totalmente del estado actual.
Desafío
Fácil¡Vamos a construir un sistema de soporte de tickets utilizando el patrón State! Crearás un ticket de soporte que pasa por diferentes etapas—desde ser abierto, hasta ser trabajado, hasta ser resuelto—con cada estado determinando qué acciones son posibles y qué sucede a continuación.
Organizarás tu código en tres archivos:
state.go: Define tu interfaz de estado y los estados concretos que representan cada etapa del ciclo de vida de un ticket.Crea una interfaz
TicketStatecon un métodoHandle(t *Ticket) stringque procese el ticket y potencialmente lo transicione al siguiente estado.Implementa tres estados:
OpenState— cuando se maneja, transiciona el ticket aInProgressStatey devuelveTicket opened, assigning to support teamInProgressState— cuando se maneja, transiciona aResolvedStatey devuelveWorking on ticket, issue resolvedResolvedState— cuando se maneja, permanece en el mismo estado y devuelveTicket already resolved
ticket.go: Crea tu estructura de ticket que mantiene el estado actual y delega el comportamiento a él.Construye una estructura
Ticketcon un campoID(string) y un campostate(TicketState). Añade estos métodos:SetState(s TicketState)para cambiar el estado actual del ticketProcess() stringque delega al método Handle del estado actual
Crea un constructor
NewTicket(id string) *Ticketque devuelva un ticket comenzando en elOpenState.main.go: Demuestra cómo la misma acción produce diferentes resultados basados en el estado actual del ticket.Lee un ID de ticket y el número de veces que se debe procesar el ticket. Crea un nuevo ticket con ese ID, luego llama a
Process()el número especificado de veces, imprimiendo cada resultado en una línea separada.
Se proporcionarán las siguientes entradas:
- Línea 1: ID del Ticket
- Línea 2: Número de veces para procesar el ticket
Por ejemplo, dado:
TKT-001
3Tu salida debería ser:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedY dado:
TKT-500
5Tu salida debería ser:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedY dado:
ISSUE-42
1Tu salida debería ser:
Ticket opened, assigning to support teamObserva cómo llamar a Process() en el mismo ticket produce resultados diferentes cada vez—el comportamiento del ticket cambia a medida que se mueve a través de los estados. Una vez resuelto, permanece resuelto sin importar cuántas veces lo proceses. El objeto ticket parece cambiar su comportamiento, ¡pero en realidad está delegando en diferentes objetos de estado!
Hoja de referencia
El patrón State permite que un objeto cambie su comportamiento cuando cambia su estado interno. El objeto delega el comportamiento a objetos de estado separados que representan diferentes estados.
Define una interfaz de estado:
type State interface {
Handle(d *Document) string
}Crea un objeto de contexto que mantenga el estado actual:
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Implementa estados concretos que encapsulen el comportamiento específico del 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 qué sucede y realiza la transición al siguiente estado:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Borrador enviado para moderación
fmt.Println(doc.Publish()) // Moderación aprobada, ahora publicado
fmt.Println(doc.Publish()) // Ya publicadoEl patrón State es ideal para objetos con distintos modos de operación donde el comportamiento depende completamente del estado actual.
Pruébalo tú mismo
package main
import (
"fmt"
)
func main() {
// Leer entrada
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: Create a new ticket con el ticketID dado
// TODO: Procesar el ticket el número especificado de veces
// y mostrar cada resultado en una línea separada
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos11Biblioteca estándar y POO
io.Reader e io.WriterInterfaz sort.InterfaceInterfaz fmt.Stringerencoding/json con StructsInterfaz http.HandlerRepaso - Modelos de REST API14Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeMiddleware como Decorator3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool