Injeção de Dependência
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 onde uma struct recebe suas dependências de fora, em vez de criá-las internamente. Em Go, interfaces tornam esse padrão natural e poderoso.
Em vez de codificar rigidamente uma implementação específica dentro de uma struct, você aceita uma interface. Isso permite que você troque as 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 mock notifier que não envia mensagens de fato. Em produção, você injeta a implementação real. A struct permanece inalterada em ambos os cenários.
Desafio
FácilVamos construir um sistema de log que demonstra o poder da injeção de dependência. Você criará um serviço que pode gravar logs em diferentes destinos — sem que o serviço saiba ou se importe para onde esses logs 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ê aoAppServiceum 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 ambos os 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 console logger
- Linha 2: Nome do arquivo do file logger
- Linha 3: Tarefa a ser processada
Por exemplo, dados INFO, app.log e user authentication, sua saída deve ser:
[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authenticationObserve como o 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ência: o mesmo código de serviço funciona com implementações de log completamente diferentes.
Folha de consulta
Injeção de Dependência é uma técnica onde uma struct recebe suas dependências de fora em vez de criá-las internamente. Em Go, interfaces tornam esse padrão natural e poderoso.
Defina uma interface para a dependência:
type Notifier interface {
Send(message string) string
}
Crie implementações concretas da interface:
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
}
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)
}
Injete a dependência ao criar o serviço:
emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book")) // Email: Order placed: Book
smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone")) // SMS: Order placed: Phone
Esta abordagem torna o código mais flexível e testável, permitindo que diferentes implementações sejam trocadas sem alterar o código da struct.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lê o prefixo do logger de console
prefix, _ := reader.ReadString('\n')
prefix = prefix[:len(prefix)-1]
// Lê o nome do arquivo do logger de arquivo
filename, _ := reader.ReadString('\n')
filename = filename[:len(filename)-1]
// Lê a tarefa para 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 filename
// 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 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 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