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 desligadaO 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.
Desafio
FácilVamos 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
Commandcom dois métodos:Execute() stringpara realizar a ação eUndo() stringpara 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 retornaInserted: [text]. Undo remove esse texto do final e retornaUndone 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 retornaDeleted: [removed text]. Undo os restaura e retornaUndone 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
TextEditorcom um campoContent(string) e métodos paraAppend(text string)eDeleteLast(count int) string(retorna o texto excluído).Construa uma struct
EditorInvokerque 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 resultadoUndoLast() string— remove o último comando do histórico, chama seu Undo e retorna o resultado. Se o histórico estiver vazio, retorneNothing 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,deleteouundo). 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 comoFinal: [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
undoSua saída deve ser:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloE 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
TestSua saída deve ser:
Nothing to undo
Inserted: Test
Final: TestObserve 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 offPara 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)
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser11Biblioteca Padrão & POO
io.Reader & io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRecapitulação - Modelos de API REST14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool