Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 interface Logger que exija um método Log(message string) string. Em seguida, crie duas implementações diferentes de logger:
    • ConsoleLogger com um campo Prefix — seu método Log retorna [CONSOLE] [Prefix]: [message]
    • FileLogger com um campo Filename — seu método Log retorna [FILE:[Filename]] [message]
  • service.go: Crie uma struct AppService que dependa da interface Logger (não de um tipo concreto). Inclua uma função construtora NewAppService que aceite um Logger e retorne um ponteiro para AppService. Dê ao AppService um método chamado DoWork(task string) string que use o logger injetado para registrar a mensagem Processing: [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 de AppService e chame DoWork em 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 authentication

Observe 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")
}
quiz iconTeste seus conhecimentos

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