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 te permite definir una familia de algoritmos, encapsular cada uno y hacer que sean intercambiables en tiempo de ejecución. A diferencia de Observer, donde varios objetos reaccionan a un evento, Strategy permite que un solo objeto cambie dinámicamente entre diferentes comportamientos.
En Go, implementamos Strategy definiendo una interfaz para el algoritmo e inyectando diferentes implementaciones en una estructura 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 de contexto contiene una referencia a la interfaz de estrategia, lo que permite cambiar el algoritmo sin modificar 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 durante la ejecución:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Pagado 100.00 vía Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Pagado 50.00 vía PayPalStrategy es ideal cuando tienes varias formas de realizar una operación y necesitas seleccionar una en función de la entrada del usuario, la configuración o las condiciones de ejecución. Los casos de uso comunes incluyen algoritmos de ordenación, métodos de compresión y reglas de validación.
Desafío
Fácil¡Construyamos un sistema de formato de texto usando el patrón Strategy! Crearás diferentes estrategias de formato que se pueden intercambiar en tiempo de ejecución, lo que permitirá que el mismo procesador de texto produzca distintos 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 concretas de formato.Crea una interfaz
TextFormattercon un métodoFormat(text string) stringque transforme el texto según las reglas de la estrategia.Implementa tres estrategias de formato:
UppercaseFormatter: convierte el texto completamente a mayúsculasSnakeCaseFormatter: convierte los espacios en guiones bajos y convierte el texto a minúsculasTitleFormatter: escribe 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 formato.Construye una estructura
TextProcessorque contenga una estrategiaTextFormatter. Añade estos métodos:SetFormatter(f TextFormatter)para cambiar la estrategia de formato activaProcess(text string) stringque aplica el formateador actual al texto
Crea también un constructor
NewTextProcessor()que devuelva un procesador sin ningún formateador inicial establecido.main.go: Demuestra cómo cambiar de estrategia en tiempo de ejecución.Lee un texto y después lee una cantidad de operaciones de formato. Para cada operación, lee el tipo de formato (
upper,snakeotitle), establece el formateador correspondiente en tu procesador, procesa el texto original e imprime el resultado.
Se proporcionarán las siguientes entradas:
- Línea 1: El texto que se va a formatear
- Línea 2: Número de operaciones de formato
- Líneas siguientes: Tipo de formato para cada operación
Por ejemplo, dada la siguiente entrada:
hello world today
3
upper
snake
titleTu salida debería ser:
HELLO WORLD TODAY
hello_world_today
Hello World TodayY dada la siguiente entrada:
Go Programming Language
2
snake
upperTu salida debería ser:
go_programming_language
GO PROGRAMMING LANGUAGEY dada la siguiente entrada:
design patterns are useful
1
titleTu salida debería ser:
Design Patterns Are UsefulObserva cómo el mismo TextProcessor produce salidas completamente diferentes simplemente cambiando su estrategia de formato. Al procesador no le importa ni sabe qué formateador específico está utilizando. ¡Simplemente delega en la estrategia que esté establecida actualmente!
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee el texto a formatear
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Lee el número de operaciones
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Crea un nuevo procesador de texto
processor := NewTextProcessor()
// Procesa 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"),
// establece el formateador apropiado en el processor
// e imprime 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 PoolPractica por tu cuenta: Compilador de Go online