Patrón Template Method
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 94 de 107.
El patrón Template Method define el esqueleto de un algoritmo en un tipo base, permitiendo que los subtipos sobrescriban pasos específicos sin cambiar la estructura general. Mientras que Decorator añade comportamiento envolviendo objetos, Template Method controla el flujo del algoritmo y permite la personalización de pasos individuales.
En Go, dado que no existe la herencia, implementamos este patrón utilizando la incrustación de estructuras (struct embedding) combinada con interfaces. La estructura "plantilla" define la estructura del algoritmo y llama a métodos que pueden ser personalizados:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}Las implementaciones concretas proporcionan sus propias versiones de cada paso mientras que el flujo del algoritmo permanece sin cambios:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }El método de plantilla Execute orquesta los pasos en un orden fijo:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Guardado: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Almacenado: parsed-json-dataTemplate Method es ideal cuando tienes algoritmos que comparten la misma estructura pero difieren en pasos específicos, como flujos de importación/exportación de datos, generadores de informes o frameworks de pruebas.
Desafío
Fácil¡Vamos a construir un sistema de generación de informes utilizando el patrón Template Method! Crearás un marco de trabajo donde diferentes tipos de informes siguen el mismo proceso de generación (recopilación de datos, formateo y salida del resultado), pero cada tipo de informe personaliza estos pasos de manera diferente.
Organizarás tu código en tres archivos:
report.go: Define tu interfaz y el procesador base que orquesta el algoritmo de generación de informes.Crea una interfaz
ReportGeneratorcon tres métodos que representen los pasos de la generación de informes:GatherData() string— recupera los datos brutos para el informeFormatData(data string) string— transforma los datos al formato del informeOutputReport(formatted string) string— produce el mensaje de salida final
Construye una estructura
ReportProcessorque contenga una implementación deReportGenerator. Añade un métodoGenerate() stringque ejecute los tres pasos en orden: recopilar, formatear y luego dar salida, devolviendo el resultado final.generators.go: Implementa generadores de informes concretos que personalicen cada paso.Crea dos tipos de informes:
SalesReportcon un campoRegion(string)GatherData()devuelve"sales-data-"+ el valor de la regiónFormatData(data)devuelve"SALES REPORT: "+ el valor de dataOutputReport(formatted)devuelve"Printed: "+ el valor de formatted
InventoryReportcon un campoWarehouse(string)GatherData()devuelve"inventory-"+ el valor del almacénFormatData(data)devuelve"*** "+ el valor de data +" ***"OutputReport(formatted)devuelve"Exported: "+ el valor de formatted
main.go: Demuestra cómo la misma estructura de algoritmo produce diferentes resultados según la implementación.Lee el tipo de informe (
"sales"o"inventory") y el valor de configuración (región para ventas, almacén para inventario). Crea el generador apropiado, envuélvelo en unReportProcessor, llama aGenerate()e imprime el resultado.
Se proporcionarán las siguientes entradas:
- Línea 1: Tipo de informe (
"sales"o"inventory") - Línea 2: Valor de configuración (nombre de la región o nombre del almacén)
Por ejemplo, dado:
sales
NorthTu salida debería ser:
Printed: SALES REPORT: sales-data-NorthY dado:
inventory
MainHubTu salida debería ser:
Exported: *** inventory-MainHub ***Y dado:
sales
WestTu salida debería ser:
Printed: SALES REPORT: sales-data-WestObserva cómo el ReportProcessor siempre llama a los mismos tres pasos en el mismo orden, pero cada tipo de informe proporciona su propia implementación de esos pasos. El esqueleto del algoritmo permanece fijo mientras los detalles varían: ¡ese es el patrón Template Method en acción!
Hoja de referencia
El patrón Template Method define el esqueleto de un algoritmo en un tipo base, permitiendo que los subtipos sobrescriban pasos específicos sin cambiar la estructura general.
En Go, implemente este patrón utilizando la incrustación de estructuras (struct embedding) combinada con interfaces. La estructura "template" define la estructura del algoritmo y llama a métodos que pueden ser personalizados:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}Las implementaciones concretas proporcionan sus propias versiones de cada paso mientras que el flujo del algoritmo permanece sin cambios:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }El método de plantilla orquesta los pasos en un orden fijo:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Saved: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-dataTemplate Method es ideal cuando los algoritmos comparten la misma estructura pero difieren en pasos específicos, como los flujos de importación/exportación de datos, generadores de informes o marcos de pruebas.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el tipo de informe
scanner.Scan()
reportType := scanner.Text()
// Leer el valor de configuración (región o almacén)
scanner.Scan()
configValue := scanner.Text()
// TODO: Crear el generador apropiado basado en reportType
// - Si reportType es "sales", crear un SalesReport con Region establecido en configValue
// - Si reportType es "inventory", crear un InventoryReport con Warehouse establecido en configValue
// TODO: Crear un ReportProcessor con el generador
// TODO: Llamar a Generate() e imprimir el resultado
_ = reportType
_ = configValue
fmt.Println("TODO: Generate and print the report")
}
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 Pool