Menu
Coddy logo textTech

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 publicado

State 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.

challenge icon

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 TicketState con un método Handle(t *Ticket) string que procese el ticket y potencialmente lo transicione al siguiente estado.

    Implementa tres estados:

    • OpenState — cuando se maneja, transiciona el ticket a InProgressState y devuelve Ticket opened, assigning to support team
    • InProgressState — cuando se maneja, transiciona a ResolvedState y devuelve Working on ticket, issue resolved
    • ResolvedState — cuando se maneja, permanece en el mismo estado y devuelve Ticket already resolved
  • ticket.go: Crea tu estructura de ticket que mantiene el estado actual y delega el comportamiento a él.

    Construye una estructura Ticket con un campo ID (string) y un campo state (TicketState). Añade estos métodos:

    • SetState(s TicketState) para cambiar el estado actual del ticket
    • Process() string que delega al método Handle del estado actual

    Crea un constructor NewTicket(id string) *Ticket que devuelva un ticket comenzando en el OpenState.

  • 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
3

Tu salida debería ser:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved

Y dado:

TKT-500
5

Tu salida debería ser:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved

Y dado:

ISSUE-42
1

Tu salida debería ser:

Ticket opened, assigning to support team

Observa 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 publicado

El 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
}
quiz iconPonte a prueba

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