Injeção de Dependências
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 45 de 107.
Injeção de Dependência é uma técnica na qual uma struct recebe suas dependências de fora, em vez de criá-las internamente. Em Go, as interfaces tornam esse padrão natural e poderoso.
Em vez de codificar diretamente uma implementação específica em uma struct, você aceita uma interface. Isso permite trocar implementações sem alterar o código da struct:
type Notifier interface {
Send(message string) string
}
type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
return "Email: " + message
}
type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
return "SMS: " + message
}
Agora crie uma struct que dependa da interface, não de um tipo concreto:
type OrderService struct {
notifier Notifier // dependência injetada via interface
}
func NewOrderService(n Notifier) *OrderService {
return &OrderService{notifier: n}
}
func (o *OrderService) PlaceOrder(item string) string {
return o.notifier.Send("Order placed: " + item)
}
O OrderService não sabe nem se importa se está usando e-mail ou SMS. Você injeta a dependência ao criar o serviço:
func main() {
emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book")) // Email: Pedido realizado: Book
smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone")) // SMS: Pedido realizado: Phone
}
Essa abordagem torna seu código mais flexível e testável. Durante os testes, você pode injetar um notificador simulado que não envia mensagens de verdade. Em produção, você injeta a implementação real. A estrutura permanece inalterada em ambos os cenários.
Desafio
FácilVamos criar um sistema de registro que demonstra o poder da injeção de dependências. Você criará um serviço que pode gravar registros em diferentes destinos, sem que o serviço saiba ou se importe para onde esses registros realmente vão.
Você organizará seu código em três arquivos:
logger.go: Defina uma interfaceLoggerque exija um métodoLog(message string) string. Em seguida, crie duas implementações diferentes de logger:ConsoleLoggercom um campoPrefix. Seu métodoLogretorna[CONSOLE] [Prefix]: [message]FileLoggercom um campoFilename. Seu métodoLogretorna[FILE:[Filename]] [message]
service.go: Crie uma structAppServiceque dependa da interfaceLogger(não de um tipo concreto). Inclua uma função construtoraNewAppServiceque aceite umLoggere retorne um ponteiro paraAppService. Dê aAppServiceum método chamadoDoWork(task string) stringque use o logger injetado para registrar a mensagemProcessing: [task]e retorne o que quer que o logger retorne.main.go: Leia a configuração da entrada, crie os dois tipos de logger, injete cada um em instâncias separadas deAppServicee chameDoWorkem cada serviço com a tarefa fornecida. Imprima o resultado de cada chamada de serviço.
As seguintes entradas serão fornecidas:
- Linha 1: Prefixo do logger do console
- Linha 2: Nome do arquivo do logger de arquivo
- Linha 3: Tarefa a ser processada
Por exemplo, dadas as entradas INFO, app.log e user authentication, sua saída deverá ser:
[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authenticationObserve como AppService não sabe se está registrando em um console ou em um arquivo. Ele simplesmente chama Log em qualquer logger que tenha sido injetado. Essa flexibilidade é a essência da injeção de dependências: o mesmo código de serviço funciona com implementações de registro completamente diferentes.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o prefixo do logger do console
prefix, _ := reader.ReadString('\n')
prefix = prefix[:len(prefix)-1]
// Ler o nome do arquivo do logger de arquivo
filename, _ := reader.ReadString('\n')
filename = filename[:len(filename)-1]
// Ler a tarefa a processar
task, _ := reader.ReadString('\n')
if len(task) > 0 && task[len(task)-1] == '\n' {
task = task[:len(task)-1]
}
// TODO: Criar um ConsoleLogger com o prefixo
// TODO: Criar um FileLogger com o nome do arquivo
// TODO: Criar um AppService com o ConsoleLogger injetado
// TODO: Criar outro AppService com o FileLogger injetado
// TODO: Chamar DoWork em cada serviço com a tarefa e imprimir os resultados
fmt.Println("result1")
fmt.Println("result2")
}
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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa 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 Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online