Padrão Adapter
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 92 de 107.
O padrão Adapter permite que interfaces incompatíveis trabalhem juntas ao envolver um tipo existente com uma nova interface. Enquanto o Command encapsula ações, o Adapter traduz uma interface em outra que os clientes esperam.
Imagine que você tem uma impressora legada que usa uma assinatura de método diferente da que sua aplicação espera:
// Interface alvo que sua aplicação utiliza
type Printer interface {
Print(text string) string
}
// Impressora legada com interface incompatível
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}O adaptador envolve o tipo legado e implementa a interface esperada:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Agora a impressora legada funciona perfeitamente com o código que espera a interface Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"O padrão Adapter é inestimável ao integrar bibliotecas de terceiros, trabalhar com código legado ou quando você precisa usar classes existentes que não correspondem aos seus requisitos de interface. Ele atua como uma ponte sem modificar o código original.
Desafio
FácilVamos construir um sistema de player de mídia usando o padrão Adapter! Você tem um player de áudio moderno que espera uma interface unificada, mas precisa integrar equipamentos de áudio legados que usam assinaturas de método completamente diferentes. Seu adaptador fará a ponte entre essa lacuna, permitindo que o hardware antigo funcione perfeitamente com o novo sistema.
Você organizará seu código em três arquivos:
player.go: Defina sua interface de destino e um dispositivo de áudio legado.Crie uma interface
AudioPlayerque seu sistema moderno espera, com um métodoPlay(track string) stringque recebe o nome de uma faixa e retorna uma mensagem de reprodução.Também defina um dispositivo legado que não corresponde a esta interface:
- Estrutura
VinylPlayercom um campoSpeedRPM(int) — ela possui um métodoDropNeedle(record string, side string) stringque retornaPlaying [record] ([side] side) at [speed]rpm
A interface do VinylPlayer é incompatível com o AudioPlayer — ela requer dois parâmetros e usa um nome de método completamente diferente.
- Estrutura
adapter.go: Crie seu adaptador que torna o dispositivo legado compatível.Construa uma estrutura
VinylAdapterque envolve umVinylPlayere implementa a interfaceAudioPlayer. QuandoPlayfor chamado, o adaptador deve chamar o métodoDropNeedledo player de vinil, definindoAcomo padrão para o parâmetro side.Também crie um player moderno para comparação:
- Estrutura
DigitalPlayer— seu métodoPlayretornaStreaming: [track]
- Estrutura
main.go: Demonstre ambos os players funcionando através da mesma interface.Crie uma função
PlayMusic(player AudioPlayer, track string) stringque usa a interface para tocar música — esta função não deve saber ou se importar se está usando um player digital ou um player de vinil adaptado.Leia um tipo de player (
digitalouvinyl) e, se for vinil, leia também a velocidade RPM. Em seguida, leia o nome da faixa. Crie o player apropriado (usando o adaptador para vinil), toque a faixa através da sua funçãoPlayMusice imprima o resultado.
As seguintes entradas serão fornecidas:
- Linha 1: Tipo de player (
digitalouvinyl) - Linha 2: Velocidade RPM (apenas se for vinil)
- Última linha: Nome da faixa para tocar
Por exemplo, dado:
digital
Bohemian RhapsodySua saída deve ser:
Streaming: Bohemian RhapsodyE dado:
vinyl
33
Abbey RoadSua saída deve ser:
Playing Abbey Road (A side) at 33rpmE dado:
vinyl
45
Blue MondaySua saída deve ser:
Playing Blue Monday (A side) at 45rpmObserve como a função PlayMusic funciona de forma idêntica com ambos os players — ela não tem ideia de que o player de vinil está, na verdade, sendo adaptado de uma interface completamente diferente. O adaptador traduz as chamadas da interface moderna para o formato legado nos bastidores!
Folha de consulta
O padrão Adapter permite que interfaces incompatíveis trabalhem juntas ao envolver um tipo existente com uma nova interface.
Defina uma interface de destino que sua aplicação espera:
type Printer interface {
Print(text string) string
}Um tipo legado com uma interface incompatível:
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}Crie um adaptador que envolva o tipo legado e implemente a interface de destino:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Use o adaptador para fazer o tipo legado funcionar com o código que espera a interface de destino:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"O padrão Adapter é útil para integrar bibliotecas de terceiros, trabalhar com código legado ou adaptar classes existentes para corresponder às interfaces exigidas sem modificar o código original.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic usa a interface AudioPlayer para tocar música
// Esta função funciona com qualquer implementação de AudioPlayer
// TODO: Implemente esta função para chamar player.Play(track) e retornar o resultado
func PlayMusic(player AudioPlayer, track string) string {
// TODO: Implemente esta função
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Lê o tipo de player
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// Lê a velocidade RPM para vinil
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// Lê o nome da faixa
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Crie um VinylPlayer com o RPM fornecido
// TODO: Envolva-o em um VinylAdapter
// TODO: Atribua à variável player
_ = rpm // Remova esta linha quando você usar rpm
} else {
// Lê o nome da faixa
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Crie um DigitalPlayer
// TODO: Atribua à variável player
}
// TODO: Chame PlayMusic e imprima o resultado
_ = player // Remova esta linha quando você usar player
_ = track // Remova esta linha quando você usar track
}
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