Patrón Command
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 91 de 107.
El patrón Command encapsula una solicitud como un objeto, lo que te permite parametrizar operaciones, ponerlas en cola o admitir la funcionalidad de deshacer. Mientras que Strategy encapsula algoritmos, Command encapsula acciones completas junto con sus parámetros.
En Go, definimos una interfaz Command con un método Execute, después creamos comandos concretos que contienen toda la información necesaria para realizar una acción:
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"
}Un invocador almacena y ejecuta comandos sin saber qué hacen:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}El invocador está completamente desacoplado del receptor (la luz):
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Luz encendida
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Luz apagadaCommand es ideal para implementar sistemas de deshacer/rehacer, colas de tareas o grabación de macros cuando necesitas almacenar, retrasar o volver a ejecutar operaciones.
Desafío
Fácil¡Construyamos un editor de texto con funcionalidad para deshacer usando el patrón Command! Crearás comandos que encapsulan operaciones de texto, lo que permite que el editor ejecute acciones y las revierta: un caso de uso clásico que demuestra por qué los comandos son tan potentes.
Organizarás tu código en tres archivos:
command.go: Define la interfaz de comandos y los comandos concretos para editar texto.Crea una interfaz
Commandcon dos métodos:Execute() stringpara realizar la acción yUndo() stringpara revertirla.Implementa dos tipos de comandos que operen sobre un
*TextEditor:InsertCommand: contiene el puntero al editor y el texto que se insertará. Execute agrega el texto al contenido del editor y devuelveInserted: [text]. Undo elimina ese texto del final y devuelveUndone insert: [text]DeleteCommand: contiene el puntero al editor y la cantidad de caracteres que se eliminarán del final. Execute elimina esos caracteres (guardándolos para deshacer) y devuelveDeleted: [removed text]. Undo los restaura y devuelveUndone delete: [restored text]
editor.go: Crea el editor de texto (el receptor) y el invocador que gestiona el historial de comandos.Crea una estructura
TextEditorcon un campoContent(string) y los métodosAppend(text string)yDeleteLast(count int) string(que devuelve el texto eliminado).Crea una estructura
EditorInvokerque contenga una lista de comandos ejecutados como historial. Agrega estos métodos:ExecuteCommand(cmd Command) string: ejecuta el comando, lo agrega al historial y devuelve el resultadoUndoLast() string: elimina el último comando del historial, llama a su método Undo y devuelve el resultado. Si el historial está vacío, devuelveNothing to undo
main.go: Demuestra el sistema de comandos con una serie de operaciones.Lee una cantidad de operaciones. Para cada operación, lee el tipo de operación (
insert,deleteoundo). Para insert, también lee el texto que se insertará. Para delete, lee la cantidad de caracteres. Ejecuta cada operación mediante el invocador e imprime el resultado. Después de todas las operaciones, imprime el contenido final del editor comoFinal: [content].
Se proporcionarán las siguientes entradas:
- Línea 1: cantidad de operaciones
- Para cada operación: tipo de operación y, después, datos adicionales si se necesitan (texto para insert, cantidad para delete)
Por ejemplo, dada esta entrada:
5
insert
Hello
insert
World
delete
3
undo
undoLa salida debería ser:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloY dada esta entrada:
4
insert
Go
insert
Lang
undo
insert
!La salida debería ser:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!Y dada esta entrada:
2
undo
insert
TestLa salida debería ser:
Nothing to undo
Inserted: Test
Final: TestObserva cómo cada comando encapsula todo lo necesario para realizar y revertir su acción. El invocador no sabe qué hacen los comandos. ¡Solo los ejecuta y mantiene el historial para permitir deshacer!
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee el número de operaciones
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Crea el editor y el invocador
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// Lee el tipo de operación
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// Lee el texto que se va a insertar
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: Crea un InsertCommand y ejecútalo mediante el invocador
_ = text
_ = editor
case "delete":
// Lee la cantidad de caracteres que se van a eliminar
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: Crea un DeleteCommand y ejecútalo mediante el invocador
_ = count
case "undo":
// TODO: Llama a UndoLast en el invocador
}
fmt.Println(result)
}
// Imprime el contenido final
fmt.Printf("Final: %s\n", editor.Content)
}
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
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos11Biblioteca estándar y POO
io.Reader e io.WriterInterfaz sort.InterfaceInterfaz fmt.Stringerencoding/json con StructsInterfaz http.HandlerRepaso - Modelos de REST API14Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeMiddleware como Decorator3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker PoolPractica por tu cuenta: Compilador de Go online