Patrón Strategy
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 90 de 107.
El patrón Strategy le permite definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables en tiempo de ejecución. A diferencia de Observer, donde múltiples objetos reaccionan a un evento, Strategy permite que un solo objeto cambie entre diferentes comportamientos de forma dinámica.
En Go, implementamos el patrón Strategy definiendo una interfaz para el algoritmo e inyectando diferentes implementaciones en un struct de contexto:
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}La estructura context mantiene una referencia a la interfaz strategy, lo que permite intercambiar el algoritmo sin cambiar el código del contexto:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Ahora puedes cambiar el método de pago en tiempo de ejecución:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Pagado 100.00 vía Tarjeta de Crédito
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Pagado 50.00 vía PayPalStrategy es ideal cuando tienes múltiples formas de realizar una operación y necesitas seleccionar una basada en la entrada del usuario, la configuración o las condiciones de tiempo de ejecución. Los casos de uso comunes incluyen algoritmos de ordenamiento, métodos de compresión y reglas de validación.
Desafío
Fácil¡Vamos a construir un sistema de formateo de texto utilizando el patrón Strategy! Crearás diferentes estrategias de formateo que se pueden intercambiar en tiempo de ejecución, permitiendo que el mismo procesador de texto produzca diferentes estilos de salida sin cambiar su código principal.
Organizarás tu código en tres archivos:
formatter.go: Define tu interfaz de estrategia y las estrategias de formateo concretas.Crea una interfaz
TextFormattercon un métodoFormat(text string) stringque transforme el texto según las reglas de la estrategia.Implementa tres estrategias de formateo:
UppercaseFormatter— convierte el texto a mayúsculasSnakeCaseFormatter— convierte los espacios en guiones bajos y pone el texto en minúsculasTitleFormatter— pone en mayúscula la primera letra de cada palabra (las palabras están separadas por espacios)
processor.go: Crea tu estructura de contexto que utiliza la estrategia de formateo.Construye una estructura
TextProcessorque contenga una estrategiaTextFormatter. Añade estos métodos:SetFormatter(f TextFormatter)para cambiar la estrategia de formateo activaProcess(text string) stringque aplica el formateador actual al texto
También crea un constructor
NewTextProcessor()que devuelva un procesador sin ningún formateador inicial configurado.main.go: Demuestra el intercambio de estrategias en tiempo de ejecución.Lee un fragmento de texto, luego lee un recuento de operaciones de formato. Para cada operación, lee el tipo de formato (
upper,snake, otitle), establece el formateador apropiado en tu procesador, procesa el texto original e imprime el resultado.
Se proporcionarán las siguientes entradas:
- Línea 1: El texto a formatear
- Línea 2: Número de operaciones de formato
- Líneas siguientes: Tipo de formato para cada operación
Por ejemplo, dado:
hello world today
3
upper
snake
titleTu salida debería ser:
HELLO WORLD TODAY
hello_world_today
Hello World TodayY dado:
Go Programming Language
2
snake
upperTu salida debería ser:
go_programming_language
GO PROGRAMMING LANGUAGEY dado:
design patterns are useful
1
titleTu salida debería ser:
Design Patterns Are UsefulObserva cómo el mismo TextProcessor produce salidas completamente diferentes simplemente intercambiando su estrategia de formateador. El procesador no sabe ni le importa qué formateador específico está utilizando; ¡simplemente delega en cualquier estrategia que esté configurada actualmente!
Hoja de referencia
El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables en tiempo de ejecución. Permite que un solo objeto cambie entre diferentes comportamientos de forma dinámica.
Define una interfaz para el algoritmo:
type PaymentStrategy interface {
Pay(amount float64) string
}Implementa estrategias concretas:
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}Crea una estructura de contexto que mantenga una referencia a la interfaz de la estrategia:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Cambia las estrategias en tiempo de ejecución:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Paid 100.00 via Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Paid 50.00 via PayPalStrategy es ideal cuando tienes múltiples formas de realizar una operación y necesitas seleccionar una basada en la entrada del usuario, la configuración o las condiciones en tiempo de ejecución.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el texto a formatear
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Leer el número de operaciones
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Crear un nuevo procesador de texto
processor := NewTextProcessor()
// Procesar cada operación de formato
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Basado en formatType ("upper", "snake", o "title"),
// establecer el formateador apropiado en el procesador
// e imprimir el texto procesado
}
}
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 archivos3Punteros 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 Pool