Padrão Strategy
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 90 de 107.
O padrão Strategy permite que você defina uma família de algoritmos, encapsule cada um deles e os torne intercambiáveis em tempo de execução. Ao contrário do Observer, onde múltiplos objetos reagem a um evento, o Strategy permite que um único objeto alterne entre diferentes comportamentos dinamicamente.
Em Go, implementamos o Strategy definindo uma interface para o algoritmo e injetando diferentes implementações em uma 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)
}A struct de contexto mantém uma referência para a interface strategy, permitindo que o algoritmo seja trocado sem alterar o código do 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)
}Agora você pode alterar o método de pagamento em tempo de execução:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Pago 100.00 via Cartão de Crédito
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Pago 50.00 via PayPalO padrão Strategy é ideal quando você tem várias maneiras de realizar uma operação e precisa selecionar uma com base na entrada do usuário, configuração ou condições de tempo de execução. Casos de uso comuns incluem algoritmos de ordenação, métodos de compressão e regras de validação.
Desafio
FácilVamos construir um sistema de formatação de texto usando o padrão Strategy! Você criará diferentes estratégias de formatação que podem ser trocadas em tempo de execução, permitindo que o mesmo processador de texto produza diferentes estilos de saída sem alterar seu código principal.
Você organizará seu código em três arquivos:
formatter.go: Defina sua interface de estratégia e as estratégias de formatação concretas.Crie uma interface
TextFormattercom um métodoFormat(text string) stringque transforma o texto de acordo com as regras da estratégia.Implemente três estratégias de formatação:
UppercaseFormatter— converte o texto para letras maiúsculasSnakeCaseFormatter— converte espaços em sublinhados (underscores) e deixa o texto em letras minúsculasTitleFormatter— capitaliza a primeira letra de cada palavra (palavras são separadas por espaços)
processor.go: Crie sua struct de contexto que utiliza a estratégia de formatação.Construa uma struct
TextProcessorque contém uma estratégiaTextFormatter. Adicione estes métodos:SetFormatter(f TextFormatter)para alterar a estratégia de formatação ativaProcess(text string) stringque aplica o formatador atual ao texto
Também crie um construtor
NewTextProcessor()que retorna um processador sem nenhum formatador inicial definido.main.go: Demonstre a troca de estratégias em tempo de execução.Leia um trecho de texto e, em seguida, leia uma contagem de operações de formatação. Para cada operação, leia o tipo de formato (
upper,snake, outitle), defina o formatador apropriado em seu processador, processe o texto original e imprima o resultado.
As seguintes entradas serão fornecidas:
- Linha 1: O texto a ser formatado
- Linha 2: Número de operações de formato
- Linhas seguintes: Tipo de formato para cada operação
Por exemplo, dado:
hello world today
3
upper
snake
titleSua saída deve ser:
HELLO WORLD TODAY
hello_world_today
Hello World TodayE dado:
Go Programming Language
2
snake
upperSua saída deve ser:
go_programming_language
GO PROGRAMMING LANGUAGEE dado:
design patterns are useful
1
titleSua saída deve ser:
Design Patterns Are UsefulObserve como o mesmo TextProcessor produz saídas completamente diferentes apenas trocando sua estratégia de formatador. O processador não sabe nem se importa com qual formatador específico está usando — ele simplesmente delega para qualquer estratégia que esteja definida no momento!
Folha de consulta
O padrão Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis em tempo de execução. Ele permite que um único objeto alterne entre diferentes comportamentos dinamicamente.
Defina uma interface para o algoritmo:
type PaymentStrategy interface {
Pay(amount float64) string
}Implemente estratégias 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)
}Crie uma struct de contexto que mantém uma referência para a interface da estratégia:
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)
}Altere as estratégias em tempo de execução:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Pago 100.00 via Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Pago 50.00 via PayPalO Strategy é ideal quando você tem várias maneiras de realizar uma operação e precisa selecionar uma com base na entrada do usuário, configuração ou condições de tempo de execução.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o texto para formatar
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Ler o número de operações
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Criar um novo processador de texto
processor := NewTextProcessor()
// Processar cada operação de formatação
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Com base no formatType ("upper", "snake" ou "title"),
// definir o formatador apropriado no processador
// e imprimir o texto processado
}
}
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érica13Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Mergulho 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 Parser3Ponteiros 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