Padrão Template Method
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 94 de 107.
O padrão Template Method define o esqueleto de um algoritmo em um tipo base, permitindo que subtipos sobrescrevam etapas específicas sem alterar a estrutura geral. Enquanto o Decorator adiciona comportamento ao envolver objetos, o Template Method controla o fluxo do algoritmo e permite a customização de etapas individuais.
Em Go, como não há herança, implementamos esse padrão usando composição de structs (struct embedding) combinada com interfaces. A struct "template" define a estrutura do algoritmo e chama métodos que podem 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)
}Implementações concretas fornecem suas próprias versões de cada etapa, enquanto o fluxo do algoritmo permanece inalterado:
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 }O método template Execute orquestra as etapas em uma ordem fixa:
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 é ideal quando você tem algoritmos que compartilham a mesma estrutura, mas diferem em etapas específicas, como pipelines de importação/exportação de dados, geradores de relatórios ou frameworks de teste.
Desafio
FácilVamos construir um sistema de geração de relatórios usando o padrão Template Method! Você criará um framework onde diferentes tipos de relatórios seguem o mesmo processo de geração — coleta de dados, formatação e saída do resultado — mas cada tipo de relatório personaliza essas etapas de forma diferente.
Você organizará seu código em três arquivos:
report.go: Defina sua interface e o processador base que orquestra o algoritmo de geração de relatórios.Crie uma interface
ReportGeneratorcom três métodos que representam as etapas da geração de relatórios:GatherData() string— recupera os dados brutos para o relatórioFormatData(data string) string— transforma os dados no formato do relatórioOutputReport(formatted string) string— produz a mensagem de saída final
Construa uma struct
ReportProcessorque contém uma implementação deReportGenerator. Adicione um métodoGenerate() stringque executa as três etapas em ordem: coletar, formatar e, em seguida, gerar a saída — retornando o resultado final.generators.go: Implemente geradores de relatórios concretos que personalizam cada etapa.Crie dois tipos de relatório:
SalesReportcom um campoRegion(string)GatherData()retorna"sales-data-"+ a regiãoFormatData(data)retorna"SALES REPORT: "+ os dadosOutputReport(formatted)retorna"Printed: "+ o conteúdo formatado
InventoryReportcom um campoWarehouse(string)GatherData()retorna"inventory-"+ o armazémFormatData(data)retorna"*** "+ os dados +" ***"OutputReport(formatted)retorna"Exported: "+ o conteúdo formatado
main.go: Demonstre como a mesma estrutura de algoritmo produz resultados diferentes com base na implementação.Leia o tipo de relatório (
"sales"ou"inventory") e o valor de configuração (região para vendas, armazém para inventário). Crie o gerador apropriado, envolva-o em umReportProcessor, chameGenerate()e imprima o resultado.
As seguintes entradas serão fornecidas:
- Linha 1: Tipo de relatório (
"sales"ou"inventory") - Linha 2: Valor de configuração (nome da região ou nome do armazém)
Por exemplo, dado:
sales
NorthSua saída deve ser:
Printed: SALES REPORT: sales-data-NorthE dado:
inventory
MainHubSua saída deve ser:
Exported: *** inventory-MainHub ***E dado:
sales
WestSua saída deve ser:
Printed: SALES REPORT: sales-data-WestObserve como o ReportProcessor sempre chama as mesmas três etapas na mesma ordem, mas cada tipo de relatório fornece sua própria implementação dessas etapas. O esqueleto do algoritmo permanece fixo enquanto os detalhes variam — esse é o padrão Template Method em ação!
Folha de consulta
O padrão Template Method define o esqueleto de um algoritmo em um tipo base, permitindo que subtipos sobrescrevam etapas específicas sem alterar a estrutura geral.
Em Go, implemente este padrão usando incorporação de structs (struct embedding) combinada com interfaces. A struct "template" define a estrutura do algoritmo e chama métodos que podem 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)
}Implementações concretas fornecem suas próprias versões de cada etapa, enquanto o fluxo do algoritmo permanece inalterado:
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 }O template method orquestra as etapas em uma ordem fixa:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Saved: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-dataO Template Method é ideal quando algoritmos compartilham a mesma estrutura, mas diferem em etapas específicas, como pipelines de importação/exportação de dados, geradores de relatórios ou frameworks de teste.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler o tipo de relatório
scanner.Scan()
reportType := scanner.Text()
// Ler o valor de configuração (região ou armazém)
scanner.Scan()
configValue := scanner.Text()
// TODO: Criar o gerador apropriado com base no reportType
// - Se reportType for "sales", criar um SalesReport com Region definido como configValue
// - Se reportType for "inventory", criar um InventoryReport com Warehouse definido como configValue
// TODO: Criar um ReportProcessor com o gerador
// TODO: Chamar Generate() e imprimir o resultado
_ = reportType
_ = configValue
fmt.Println("TODO: Generate and print the report")
}
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