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 que el objeto ha cambiado de clase. Mientras que Template Method controla los pasos del algoritmo, State encapsula el comportamiento específico del estado en objetos separados y delega en el 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é ocurre y qué estado viene después:

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 a moderación
fmt.Println(doc.Publish())  // Moderación aprobada, ahora publicado
fmt.Println(doc.Publish())  // Ya publicado

State es ideal para objetos con modos de operación distintos, como flujos de trabajo de procesamiento de pedidos, componentes de UI o controladores de conexión cuyo comportamiento depende completamente del estado actual.

challenge icon

Desafío

Fácil

¡Construyamos un sistema de soporte de tickets usando el patrón State! Crearás un ticket de soporte que pasa por diferentes etapas, desde que se abre, pasando por el trabajo en él, hasta que se resuelve, donde cada estado determina qué acciones son posibles y qué sucede después.

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 cambie al siguiente estado.

    Implementa tres estados:

    • OpenState: cuando se procesa, cambia el ticket a InProgressState y devuelve Ticket opened, assigning to support team
    • InProgressState: cuando se procesa, cambia a ResolvedState y devuelve Working on ticket, issue resolved
    • ResolvedState: cuando se procesa, permanece en el mismo estado y devuelve Ticket already resolved
  • ticket.go: Crea tu estructura de ticket, que contiene el estado actual y delega el comportamiento en é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 en el método Handle del estado actual

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

  • main.go: Demuestra cómo la misma acción produce resultados diferentes según el estado actual del ticket.

    Lee un ID de ticket y el número de veces que se debe procesar el ticket. Crea un ticket nuevo con ese ID y, después, llama a Process() el número de veces especificado, mostrando 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 que se debe procesar el ticket

Por ejemplo, dada la siguiente entrada:

TKT-001
3

Tu salida debería ser:

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

Y dada la siguiente entrada:

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 dada la siguiente entrada:

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 pasa por 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!

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: Crear un nuevo ticket con el ID dado

	// TODO: Procesar el ticket el número de veces especificado
	// e imprimir 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

Practica por tu cuenta: Compilador de Go online