Menu
Coddy logo textTech

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

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

challenge icon

Desafio

Fácil

Vamos 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 TicketState com um método Handle(t *Ticket) string que processa o ticket e potencialmente o transiciona para o próximo estado.

    Implemente três estados:

    • OpenState — quando manipulado, transiciona o ticket para InProgressState e retorna Ticket opened, assigning to support team
    • InProgressState — quando manipulado, transiciona para ResolvedState e retorna Working on ticket, issue resolved
    • ResolvedState — quando manipulado, permanece no mesmo estado e retorna Ticket already resolved
  • ticket.go: Crie sua struct de ticket que mantém o estado atual e delega o comportamento a ele.

    Construa uma struct Ticket com um campo ID (string) e um campo state (TicketState). Adicione estes métodos:

    • SetState(s TicketState) para alterar o estado atual do ticket
    • Process() string que delega para o método Handle do estado atual

    Crie um construtor NewTicket(id string) *Ticket que retorna um ticket começando no OpenState.

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

Sua saída deve ser:

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

E dado:

TKT-500
5

Sua saída deve ser:

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

E dado:

ISSUE-42
1

Sua saída deve ser:

Ticket opened, assigning to support team

Observe 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á publicado

O 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
}
quiz iconTeste seus conhecimentos

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