Menu
Coddy logo textTech

Padrão Command

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 91 de 107.

O padrão Command encapsula uma solicitação como um objeto, permitindo que você parametrize operações, as coloque em fila ou suporte a funcionalidade de desfazer. Enquanto o Strategy encapsula algoritmos, o Command encapsula ações inteiras junto com seus parâmetros.

Em Go, definimos uma interface Command com um método Execute e, em seguida, criamos comandos concretos que contêm todas as informações necessárias para realizar uma ação:

type Command interface {
    Execute() string
}

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

Um invoker armazena e executa comandos sem saber o que eles fazem:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

O invoker está completamente desacoplado do receiver (a Luz):

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Luz ligada

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Luz desligada

O Command é ideal para implementar sistemas de desfazer/refazer, filas de tarefas ou gravação de macros onde você precisa armazenar, atrasar ou repetir operações.

challenge icon

Desafio

Fácil

Vamos construir um editor de texto com funcionalidade de desfazer usando o padrão Command! Você criará comandos que encapsulam operações de texto, permitindo que o editor execute ações e as reverta — um caso de uso clássico que demonstra por que os comandos são tão poderosos.

Você organizará seu código em três arquivos:

  • command.go: Defina sua interface de comando e os comandos concretos de edição de texto.

    Crie uma interface Command com dois métodos: Execute() string para realizar a ação e Undo() string para revertê-la.

    Implemente dois tipos de comando que operam em um *TextEditor:

    • InsertCommand — mantém o ponteiro do editor e o texto a ser inserido. Execute anexa o texto ao conteúdo do editor e retorna Inserted: [text]. Undo remove esse texto do final e retorna Undone insert: [text]
    • DeleteCommand — mantém o ponteiro do editor e uma contagem de caracteres para excluir do final. Execute remove esses caracteres (armazenando-os para desfazer) e retorna Deleted: [removed text]. Undo os restaura e retorna Undone delete: [restored text]
  • editor.go: Crie seu editor de texto (o receptor) e o invoker que gerencia o histórico de comandos.

    Construa uma struct TextEditor com um campo Content (string) e métodos para Append(text string) e DeleteLast(count int) string (retorna o texto excluído).

    Construa uma struct EditorInvoker que mantém um slice de comandos executados como histórico. Adicione estes métodos:

    • ExecuteCommand(cmd Command) string — executa o comando, adiciona-o ao histórico e retorna o resultado
    • UndoLast() string — remove o último comando do histórico, chama seu Undo e retorna o resultado. Se o histórico estiver vazio, retorne Nothing to undo
  • main.go: Demonstre seu sistema de comandos com uma série de operações.

    Leia uma contagem de operações. Para cada operação, leia o tipo de operação (insert, delete ou undo). Para insert, leia também o texto a ser inserido. Para delete, leia a contagem de caracteres. Execute cada operação através do seu invoker e imprima o resultado. Após todas as operações, imprima o conteúdo final do editor como Final: [content].

As seguintes entradas serão fornecidas:

  • Linha 1: Número de operações
  • Para cada operação: tipo de operação, depois dados adicionais se necessário (texto para insert, contagem para delete)

Por exemplo, dado:

5
insert
Hello
insert
 World
delete
3
undo
undo

Sua saída deve ser:

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

E dado:

4
insert
Go
insert
Lang
undo
insert
!

Sua saída deve ser:

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

E dado:

2
undo
insert
Test

Sua saída deve ser:

Nothing to undo
Inserted: Test
Final: Test

Observe como cada comando encapsula tudo o que é necessário para realizar e reverter sua ação. O invoker não sabe o que os comandos fazem — ele apenas os executa e mantém o histórico para suporte ao desfazer!

Folha de consulta

O padrão Command encapsula uma solicitação como um objeto, permitindo que você parametrize operações, as enfileire ou suporte a funcionalidade de desfazer (undo).

Defina uma interface Command com um método Execute:

type Command interface {
    Execute() string
}

Crie comandos concretos que contenham todas as informações necessárias para realizar uma ação:

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

Um invocador armazena e executa comandos sem saber o que eles fazem:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

Exemplo de uso mostrando o desacoplamento entre o invocador e o receptor:

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned on

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned off

Para a funcionalidade de desfazer, adicione um método Undo à interface Command e mantenha o histórico de comandos no invocador.

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lê o número de operações
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Cria o editor e o invoker
	editor := &TextEditor{}
	invoker := &EditorInvoker{}
	
	for i := 0; i < numOps; i++ {
		// Lê o tipo de operação
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// Lê o texto para inserir
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: Criar um InsertCommand e executá-lo através do invoker
			_ = text
			_ = editor
			
		case "delete":
			// Lê a contagem de caracteres para excluir
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Criar um DeleteCommand e executá-lo através do invoker
			_ = count
			
		case "undo":
			// TODO: Chamar UndoLast no invoker
			
		}
		
		fmt.Println(result)
	}
	
	// Imprime o conteúdo final
	fmt.Printf("Final: %s\n", editor.Content)
}
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