🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Go

Interfaces em Go: introdução prática com exemplos

Desvende o funcionamento das interfaces em Go com explicações diretas, metáforas simples e exemplos práticos de código.

CrazyStack
16 min de leitura
GolangInterfacesGoProgramação

Por que isso é importante

Interfaces são fundamentais para escrita de código desacoplado, testável e reutilizável em linguagens como Go. Entender seu papel como contratos de comportamento — sem se prender à implementação — é uma habilidade essencial para qualquer desenvolvedor backend.

O que é uma interface em Go?

Em Go, uma interface define um conjunto de métodos que um tipo precisa implementar para ser considerado compatível com ela. Diferente de linguagens como Java, essa implementação é implícita: se um tipo possui os métodos pedidos, ele é automaticamente aceito.

interface_basica.go
// 🎯 Definindo uma interface simples
type Animal interface {
    FazerBarulho() string
}

// 🐕 Struct Cachorro
type Cachorro struct {
    Nome string
}

// 🐱 Struct Gato  
type Gato struct {
    Nome string
}

// 🔊 Implementação implícita para Cachorro
func (c Cachorro) FazerBarulho() string {
    return "Au au!"
}

// 🔊 Implementação implícita para Gato
func (g Gato) FazerBarulho() string {
    return "Miau!"
}

func main() {
    // ✅ Ambos tipos satisfazem a interface Animal
    var animal1 Animal = Cachorro{Nome: "Rex"}
    var animal2 Animal = Gato{Nome: "Mimi"}
    
    fmt.Println(animal1.FazerBarulho()) // Au au!
    fmt.Println(animal2.FazerBarulho()) // Miau!
}

ℹ️Atenção

Interfaces não conhecem implementações. Elas apenas exigem a presença de métodos específicos — um verdadeiro contrato de comportamento.

Metáfora simples: o contrato do jogador

Pense em um jogador de futebol assinando com um clube. O contrato não exige saber a posição do atleta, mas apenas que ele jogue conforme acordado. Interfaces seguem esse mesmo conceito: o que importa é o que será feito, não como ou por quem.

contrato_jogador.go
// ⚽ Interface representa o "contrato" do jogador
type Jogador interface {
    Jogar() string
    Treinar() string
}

// 🥅 Goleiro implementa o contrato à sua maneira
type Goleiro struct {
    Nome string
}

func (g Goleiro) Jogar() string {
    return "Defendendo o gol com as mãos"
}

func (g Goleiro) Treinar() string {
    return "Praticando defesas e reflexos"
}

// ⚽ Atacante implementa o contrato diferente
type Atacante struct {
    Nome string
}

func (a Atacante) Jogar() string {
    return "Marcando gols com os pés"
}

func (a Atacante) Treinar() string {
    return "Praticando finalizações"
}

// 🏟️ Função que aceita qualquer jogador (independente da posição)
func EscalarJogador(j Jogador) {
    fmt.Printf("Escalando: %s
", j.Jogar())
    fmt.Printf("Treinamento: %s
", j.Treinar())
}

func main() {
    goleiro := Goleiro{Nome: "Buffon"}
    atacante := Atacante{Nome: "Ronaldo"}
    
    // ✅ Ambos cumprem o "contrato" Jogador
    EscalarJogador(goleiro)
    EscalarJogador(atacante)
}

⚠️Atenção

Ao usar interfaces, você foca nos comportamentos esperados e não na implementação concreta. Isso reduz o acoplamento e permite mais flexibilidade no código.

Definindo comportamentos com interfaces

Suponha uma interface chamada Animal com um método FazerBarulho. Cachorros implementam latido, gatos fazem miau — cada um à sua maneira. O programa apenas exige que eles façam barulho, não importa como.

comportamentos_animais.go
package main

import "fmt"

// 🔊 Interface define COMPORTAMENTO, não implementação
type Animal interface {
    FazerBarulho() string
    Mover() string
}

// 🐕 Cachorro tem sua própria implementação
type Cachorro struct {
    Raca string
}

func (c Cachorro) FazerBarulho() string {
    return "Au au au!"
}

func (c Cachorro) Mover() string {
    return "Correndo com 4 patas"
}

// 🐱 Gato implementa os mesmos métodos, mas diferente
type Gato struct {
    Cor string
}

func (g Gato) FazerBarulho() string {
    return "Miau miau"
}

func (g Gato) Mover() string {
    return "Saltando silenciosamente"
}

// 🦅 Pássaro também implementa a interface
type Passaro struct {
    Especie string
}

func (p Passaro) FazerBarulho() string {
    return "Piu piu"
}

func (p Passaro) Mover() string {
    return "Voando com as asas"
}

// 📢 Função que trabalha com qualquer Animal
func FazerAnimalSeComportar(a Animal) {
    fmt.Printf("Som: %s
", a.FazerBarulho())
    fmt.Printf("Movimento: %s
", a.Mover())
    fmt.Println("---")
}

func main() {
    // 🎯 Todos são tratados como Animal, cada um se comporta diferente
    animais := []Animal{
        Cachorro{Raca: "Labrador"},
        Gato{Cor: "Preto"},
        Passaro{Especie: "Canário"},
    }
    
    for _, animal := range animais {
        FazerAnimalSeComportar(animal)
    }
}

Interface implícita em Go

Em Go, basta um tipo implementar os métodos da interface para que ele seja compatível. Isso simplifica o código e aumenta a flexibilidade; sempre que um tipo definir todos os métodos esperados, ele passa a satisfazer a interface.

interface_implicita.go
package main

import "fmt"

// 📝 Interface Writer simples
type Writer interface {
    Write(data string) error
}

// 📄 FileWriter - implementa Writer implicitamente
type FileWriter struct {
    filename string
}

func (f FileWriter) Write(data string) error {
    fmt.Printf("Escrevendo '%s' no arquivo %s
", data, f.filename)
    return nil
}

// 📧 EmailSender - TAMBÉM implementa Writer (sem declarar explicitamente)
type EmailSender struct {
    recipient string
}

func (e EmailSender) Write(data string) error {
    fmt.Printf("Enviando email para %s: %s
", e.recipient, data)
    return nil
}

// 🖨️ Printer - TAMBÉM é um Writer automaticamente
type Printer struct {
    brand string
}

func (p Printer) Write(data string) error {
    fmt.Printf("Imprimindo via %s: %s
", p.brand, data)
    return nil
}

// ✍️ Função genérica que aceita qualquer Writer
func ProcessarDados(w Writer, dados string) {
    err := w.Write(dados)
    if err != nil {
        fmt.Printf("Erro: %v
", err)
    }
}

func main() {
    // 🎯 Todos são Writers automaticamente (implementação implícita)
    file := FileWriter{filename: "dados.txt"}
    email := EmailSender{recipient: "user@example.com"}
    printer := Printer{brand: "HP"}
    
    dados := "Dados importantes para processamento"
    
    // ✅ Mesma função funciona com tipos diferentes
    ProcessarDados(file, dados)
    ProcessarDados(email, dados)
    ProcessarDados(printer, dados)
    
    // 📋 Slice de interfaces diferentes tipos
    writers := []Writer{file, email, printer}
    for i, writer := range writers {
        ProcessarDados(writer, fmt.Sprintf("Dados %d", i+1))
    }
}

Polimorfismo em ação

Interfaces permitem tratar objetos diferentes de forma uniforme. Com uma interface Forma que define o método Área, podemos utilizar tanto Círculo quanto Quadrado sem precisar se preocupar com as diferenças internas de cálculo.

polimorfismo_formas.go
package main

import (
    "fmt"
    "math"
)

// 📐 Interface Forma define contrato para figuras geométricas
type Forma interface {
    Area() float64
    Perimetro() float64
    Tipo() string
}

// ⭕ Círculo implementa Forma
type Circulo struct {
    Raio float64
}

func (c Circulo) Area() float64 {
    return math.Pi * c.Raio * c.Raio
}

func (c Circulo) Perimetro() float64 {
    return 2 * math.Pi * c.Raio
}

func (c Circulo) Tipo() string {
    return "Círculo"
}

// ⬜ Quadrado implementa Forma
type Quadrado struct {
    Lado float64
}

func (q Quadrado) Area() float64 {
    return q.Lado * q.Lado
}

func (q Quadrado) Perimetro() float64 {
    return 4 * q.Lado
}

func (q Quadrado) Tipo() string {
    return "Quadrado"
}

// 📊 Triângulo também implementa Forma
type Triangulo struct {
    Base, Altura, LadoA, LadoB, LadoC float64
}

func (t Triangulo) Area() float64 {
    return (t.Base * t.Altura) / 2
}

func (t Triangulo) Perimetro() float64 {
    return t.LadoA + t.LadoB + t.LadoC
}

func (t Triangulo) Tipo() string {
    return "Triângulo"
}

// 🧮 Função polimórfica: funciona com qualquer Forma
func CalcularPropriedades(f Forma) {
    fmt.Printf("=== %s ===
", f.Tipo())
    fmt.Printf("Área: %.2f
", f.Area())
    fmt.Printf("Perímetro: %.2f
", f.Perimetro())
    fmt.Println()
}

// 📊 Relatório de múltiplas formas
func GerarRelatorio(formas []Forma) {
    var areaTotal float64
    
    fmt.Println("📋 RELATÓRIO DE FORMAS GEOMÉTRICAS")
    fmt.Println("================================")
    
    for i, forma := range formas {
        fmt.Printf("Forma %d:
", i+1)
        CalcularPropriedades(forma)
        areaTotal += forma.Area()
    }
    
    fmt.Printf("🎯 Área total de todas as formas: %.2f
", areaTotal)
}

func main() {
    // 🎯 Polimorfismo: diferentes tipos, mesma interface
    formas := []Forma{
        Circulo{Raio: 5},
        Quadrado{Lado: 4},
        Triangulo{Base: 6, Altura: 8, LadoA: 6, LadoB: 8, LadoC: 10},
    }
    
    // ✅ Mesma função funciona para todos os tipos
    GerarRelatorio(formas)
    
    // 🔄 Tratamento uniforme de objetos diferentes
    fmt.Println("
🔄 Processamento individual:")
    for _, forma := range formas {
        CalcularPropriedades(forma)
    }
}

Info Extra

Isso é polimorfismo: múltiplos tipos com comportamentos distintos, tratados de maneira uniforme.

Desacoplamento com interfaces

Interfaces estabelecem fronteiras claras entre partes do sistema. Arquiteturas como Ports and Adapters se baseiam nesse princípio, permitindo a troca de implementações com mínimo impacto, desde que o contrato continue sendo respeitado.

desacoplamento_interfaces.go
package main

import "fmt"

// 💾 Interface para persistência (Port)
type UserRepository interface {
    Save(user User) error
    FindByID(id string) (*User, error)
    Delete(id string) error
}

// 📧 Interface para notificações (Port)  
type NotificationService interface {
    SendWelcomeEmail(userEmail string) error
    SendSMS(phone, message string) error
}

// 👤 Domain Model
type User struct {
    ID    string
    Name  string
    Email string
    Phone string
}

// 🏛️ Service/UseCase (não conhece implementações específicas)
type UserService struct {
    repo         UserRepository      // Interface, não implementação concreta
    notification NotificationService // Interface, não implementação concreta
}

func NewUserService(repo UserRepository, notif NotificationService) *UserService {
    return &UserService{
        repo:         repo,
        notification: notif,
    }
}

func (s *UserService) CreateUser(name, email, phone string) error {
    user := User{
        ID:    fmt.Sprintf("user_%d", len(name)), // ID simples para exemplo
        Name:  name,
        Email: email,
        Phone: phone,
    }
    
    // 💾 Salva usando interface (não sabe se é MySQL, Postgres, etc.)
    if err := s.repo.Save(user); err != nil {
        return fmt.Errorf("erro ao salvar usuário: %w", err)
    }
    
    // 📧 Notifica usando interface (não sabe se é SMTP, SES, etc.)
    if err := s.notification.SendWelcomeEmail(user.Email); err != nil {
        return fmt.Errorf("erro ao enviar email: %w", err)
    }
    
    return nil
}

// 📊 Adapter 1: Banco em memória
type InMemoryUserRepo struct {
    users map[string]User
}

func NewInMemoryUserRepo() *InMemoryUserRepo {
    return &InMemoryUserRepo{users: make(map[string]User)}
}

func (r *InMemoryUserRepo) Save(user User) error {
    r.users[user.ID] = user
    fmt.Printf("💾 [InMemory] Usuário %s salvo
", user.Name)
    return nil
}

func (r *InMemoryUserRepo) FindByID(id string) (*User, error) {
    user, exists := r.users[id]
    if !exists {
        return nil, fmt.Errorf("usuário não encontrado")
    }
    return &user, nil
}

func (r *InMemoryUserRepo) Delete(id string) error {
    delete(r.users, id)
    return nil
}

// 🗄️ Adapter 2: "Banco MySQL" (simulado)
type MySQLUserRepo struct{}

func (r *MySQLUserRepo) Save(user User) error {
    fmt.Printf("🗄️ [MySQL] INSERT INTO users VALUES('%s', '%s', '%s')
", 
        user.ID, user.Name, user.Email)
    return nil
}

func (r *MySQLUserRepo) FindByID(id string) (*User, error) {
    fmt.Printf("🔍 [MySQL] SELECT * FROM users WHERE id='%s'
", id)
    return &User{ID: id, Name: "User from MySQL"}, nil
}

func (r *MySQLUserRepo) Delete(id string) error {
    fmt.Printf("🗑️ [MySQL] DELETE FROM users WHERE id='%s'
", id)
    return nil
}

// 📨 Adapter 3: Email SMTP (simulado)
type SMTPNotification struct{}

func (n *SMTPNotification) SendWelcomeEmail(email string) error {
    fmt.Printf("📨 [SMTP] Enviando email de boas-vindas para %s
", email)
    return nil
}

func (n *SMTPNotification) SendSMS(phone, message string) error {
    fmt.Printf("📱 [SMTP] SMS para %s: %s
", phone, message)
    return nil
}

// 🚀 Adapter 4: AWS SES (simulado)
type AWSNotification struct{}

func (n *AWSNotification) SendWelcomeEmail(email string) error {
    fmt.Printf("☁️ [AWS SES] Welcome email sent to %s
", email)
    return nil
}

func (n *AWSNotification) SendSMS(phone, message string) error {
    fmt.Printf("☁️ [AWS SNS] SMS to %s: %s
", phone, message)
    return nil
}

func main() {
    fmt.Println("🏗️ DEMONSTRAÇÃO DE DESACOPLAMENTO COM INTERFACES")
    fmt.Println("=================================================")
    
    // 🔄 Cenário 1: InMemory + SMTP
    fmt.Println("
📂 Cenário 1: InMemory Repository + SMTP Notifications")
    inMemoryRepo := NewInMemoryUserRepo()
    smtpNotif := &SMTPNotification{}
    service1 := NewUserService(inMemoryRepo, smtpNotif)
    service1.CreateUser("João", "joao@email.com", "+5511999999999")
    
    // 🔄 Cenário 2: MySQL + AWS (mesma interface!)
    fmt.Println("
🗄️ Cenário 2: MySQL Repository + AWS Notifications")
    mysqlRepo := &MySQLUserRepo{}
    awsNotif := &AWSNotification{}
    service2 := NewUserService(mysqlRepo, awsNotif)
    service2.CreateUser("Maria", "maria@email.com", "+5511888888888")
    
    // 🔄 Cenário 3: Mix and Match
    fmt.Println("
🔀 Cenário 3: InMemory + AWS (mix and match)")
    service3 := NewUserService(inMemoryRepo, awsNotif)
    service3.CreateUser("Pedro", "pedro@email.com", "+5511777777777")
    
    fmt.Println("
✅ Mesmo código de negócio funciona com diferentes implementações!")
    fmt.Println("🎯 Isso é DESACOPLAMENTO em ação!")
}

Métodos implementados: exemplo prático

1
Passo 1: Criar struct Quadrado com campo Lado do tipo float64
2
Passo 2: Criar struct Círculo com campo Raio
3
Passo 3: Adicionar método Área() para cada struct, retornando o cálculo específico
4
Passo 4: Criar interface Forma com assinatura do método Área
5
Passo 5: Criar função calcularArea(Forma) que recebe a interface como parâmetro

Expandindo Interfaces com múltiplos métodos

Podemos evoluir a interface Forma e incluir também o método Perímetro. Se um tipo não implementar todos os métodos, ele será incompatível.

Erro comum

Remover um dos métodos do tipo — como Perímetro — gera erro de compilação, pois o contrato não é mais respeitado.

Funções genéricas usando interfaces

Ao receber uma interface como parâmetro, uma função passa a aceitar qualquer tipo compatível. Isso reduz duplicação e centraliza comportamentos.

Testes com interfaces simuladas

Interfaces também facilitam testes unitários. Você pode criar uma implementação falsa (mock) do contrato para simular comportamentos sem depender de funções reais.

testes_mocks_interfaces.go
package main

import (
    "errors"
    "fmt"
    "testing"
)

// 💳 Interface para processamento de pagamentos
type PaymentProcessor interface {
    ProcessPayment(amount float64, cardToken string) (*PaymentResult, error)
    ValidateCard(cardToken string) bool
}

type PaymentResult struct {
    TransactionID string
    Status        string
    Amount        float64
}

// 🛒 Service que usa a interface (código de produção)
type OrderService struct {
    paymentProcessor PaymentProcessor
}

func NewOrderService(processor PaymentProcessor) *OrderService {
    return &OrderService{paymentProcessor: processor}
}

func (o *OrderService) ProcessOrder(amount float64, cardToken string) (*OrderResult, error) {
    // 1️⃣ Validar cartão
    if !o.paymentProcessor.ValidateCard(cardToken) {
        return nil, errors.New("cartão inválido")
    }
    
    // 2️⃣ Processar pagamento
    payment, err := o.paymentProcessor.ProcessPayment(amount, cardToken)
    if err != nil {
        return nil, fmt.Errorf("erro no pagamento: %w", err)
    }
    
    // 3️⃣ Criar pedido
    return &OrderResult{
        OrderID:       "order_123",
        TransactionID: payment.TransactionID,
        Total:         payment.Amount,
        Status:        "confirmed",
    }, nil
}

type OrderResult struct {
    OrderID       string
    TransactionID string
    Total         float64
    Status        string
}

// 🎭 MOCK para testes (implementação falsa)
type MockPaymentProcessor struct {
    shouldValidateCard bool
    shouldFailPayment  bool
    callLog            []string
}

func (m *MockPaymentProcessor) ValidateCard(cardToken string) bool {
    m.callLog = append(m.callLog, fmt.Sprintf("ValidateCard(%s)", cardToken))
    return m.shouldValidateCard
}

func (m *MockPaymentProcessor) ProcessPayment(amount float64, cardToken string) (*PaymentResult, error) {
    m.callLog = append(m.callLog, fmt.Sprintf("ProcessPayment(%.2f, %s)", amount, cardToken))
    
    if m.shouldFailPayment {
        return nil, errors.New("falha na transação")
    }
    
    return &PaymentResult{
        TransactionID: "txn_mock_123",
        Status:        "approved",
        Amount:        amount,
    }, nil
}

// 🏭 Implementação real (simulada para exemplo)
type StripePaymentProcessor struct{}

func (s *StripePaymentProcessor) ValidateCard(cardToken string) bool {
    fmt.Printf("🌐 [Stripe] Validando cartão %s...
", cardToken)
    // Aqui faria requisição real para Stripe
    return cardToken != "invalid_card"
}

func (s *StripePaymentProcessor) ProcessPayment(amount float64, cardToken string) (*PaymentResult, error) {
    fmt.Printf("💳 [Stripe] Processando pagamento de $%.2f
", amount)
    // Aqui faria requisição real para Stripe
    
    return &PaymentResult{
        TransactionID: "stripe_txn_456",
        Status:        "approved",
        Amount:        amount,
    }, nil
}

// 🧪 TESTES
func TestOrderService_ProcessOrder_Success(t *testing.T) {
    // Arrange - Configurar mock para sucesso
    mockProcessor := &MockPaymentProcessor{
        shouldValidateCard: true,
        shouldFailPayment:  false,
    }
    
    orderService := NewOrderService(mockProcessor)
    
    // Act - Executar o teste
    result, err := orderService.ProcessOrder(100.0, "valid_card_123")
    
    // Assert - Verificar resultados
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
    
    if result == nil {
        t.Fatal("Expected result, got nil")
    }
    
    if result.Status != "confirmed" {
        t.Errorf("Expected status 'confirmed', got '%s'", result.Status)
    }
    
    if result.Total != 100.0 {
        t.Errorf("Expected total 100.0, got %.2f", result.Total)
    }
    
    // Verificar se métodos foram chamados corretamente
    expectedCalls := []string{
        "ValidateCard(valid_card_123)",
        "ProcessPayment(100.00, valid_card_123)",
    }
    
    for i, expectedCall := range expectedCalls {
        if i >= len(mockProcessor.callLog) || mockProcessor.callLog[i] != expectedCall {
            t.Errorf("Expected call %d to be '%s', got '%s'", i, expectedCall, mockProcessor.callLog[i])
        }
    }
}

func TestOrderService_ProcessOrder_InvalidCard(t *testing.T) {
    // Arrange - Mock configurado para cartão inválido
    mockProcessor := &MockPaymentProcessor{
        shouldValidateCard: false, // ❌ Cartão inválido
    }
    
    orderService := NewOrderService(mockProcessor)
    
    // Act
    result, err := orderService.ProcessOrder(100.0, "invalid_card")
    
    // Assert
    if err == nil {
        t.Error("Expected error for invalid card")
    }
    
    if result != nil {
        t.Error("Expected nil result for invalid card")
    }
    
    if err.Error() != "cartão inválido" {
        t.Errorf("Expected 'cartão inválido', got '%s'", err.Error())
    }
}

func TestOrderService_ProcessOrder_PaymentFailure(t *testing.T) {
    // Arrange - Mock configurado para falha no pagamento
    mockProcessor := &MockPaymentProcessor{
        shouldValidateCard: true,  // ✅ Cartão válido
        shouldFailPayment:  true,  // ❌ Mas pagamento falha
    }
    
    orderService := NewOrderService(mockProcessor)
    
    // Act
    result, err := orderService.ProcessOrder(100.0, "valid_card")
    
    // Assert
    if err == nil {
        t.Error("Expected error for payment failure")
    }
    
    if result != nil {
        t.Error("Expected nil result for payment failure")
    }
}

func main() {
    fmt.Println("🧪 DEMONSTRAÇÃO DE TESTES COM INTERFACES")
    fmt.Println("=======================================")
    
    // 🎯 Demonstração com mock (para testes)
    fmt.Println("
🎭 Teste com Mock:")
    mockProcessor := &MockPaymentProcessor{
        shouldValidateCard: true,
        shouldFailPayment:  false,
    }
    orderService := NewOrderService(mockProcessor)
    result, err := orderService.ProcessOrder(99.99, "test_card_456")
    
    if err != nil {
        fmt.Printf("❌ Erro: %v
", err)
    } else {
        fmt.Printf("✅ Pedido criado: %+v
", result)
    }
    
    fmt.Printf("📋 Calls no mock: %v
", mockProcessor.callLog)
    
    // 🌐 Demonstração com implementação real (simulada)
    fmt.Println("
🌐 Produção com Stripe:")
    stripeProcessor := &StripePaymentProcessor{}
    orderService2 := NewOrderService(stripeProcessor)
    result2, err2 := orderService2.ProcessOrder(99.99, "stripe_card_789")
    
    if err2 != nil {
        fmt.Printf("❌ Erro: %v
", err2)
    } else {
        fmt.Printf("✅ Pedido criado: %+v
", result2)
    }
    
    fmt.Println("
🎯 Mesma lógica de negócio, diferentes implementações!")
    fmt.Println("🧪 Mocks permitem testes rápidos e confiáveis!")
}

ℹ️Dica de Teste

Crie mocks que retornam valores previsíveis e use-os para validar a lógica de funções que dependem da interface.

Interfaces e Clean Architecture

Em arquiteturas limpas, interfaces separam comportamentos das dependências externas. Isso aumenta a testabilidade do domínio e a flexibilidade nas trocas de componentes.

clean_architecture_interfaces.go
package main

import (
    "fmt"
    "time"
)

// ========================================
// 🏛️ DOMAIN LAYER (Entidades e Interfaces)
// ========================================

// 📋 Entidade de domínio
type Product struct {
    ID          string
    Name        string
    Price       float64
    Stock       int
    CreatedAt   time.Time
}

// 📝 Value Object
type OrderID struct {
    Value string
}

// 🔥 Agregado raiz
type Order struct {
    ID       OrderID
    Products []Product
    Total    float64
    Status   string
}

// 🚪 Interfaces de domínio (Ports) - definidas no domínio
type ProductRepository interface {
    FindByID(id string) (*Product, error)
    Save(product *Product) error
    UpdateStock(id string, newStock int) error
}

type OrderRepository interface {
    Save(order *Order) error
    FindByID(orderID OrderID) (*Order, error)
}

type EmailService interface {
    SendOrderConfirmation(order *Order) error
}

type InventoryService interface {
    ReserveStock(productID string, quantity int) error
    ReleaseStock(productID string, quantity int) error
}

// ========================================
// 🏭 USE CASES (Application Layer)
// ========================================

// 🛒 Use Case para processar pedido
type ProcessOrderUseCase struct {
    productRepo   ProductRepository  // Depende da interface, não implementação
    orderRepo     OrderRepository    // Depende da interface, não implementação
    emailService  EmailService       // Depende da interface, não implementação
    inventory     InventoryService   // Depende da interface, não implementação
}

func NewProcessOrderUseCase(
    productRepo ProductRepository,
    orderRepo OrderRepository,
    emailService EmailService,
    inventory InventoryService,
) *ProcessOrderUseCase {
    return &ProcessOrderUseCase{
        productRepo:  productRepo,
        orderRepo:    orderRepo,
        emailService: emailService,
        inventory:    inventory,
    }
}

func (uc *ProcessOrderUseCase) Execute(productID string, quantity int) (*Order, error) {
    // 1️⃣ Buscar produto (via interface)
    product, err := uc.productRepo.FindByID(productID)
    if err != nil {
        return nil, fmt.Errorf("produto não encontrado: %w", err)
    }
    
    // 2️⃣ Verificar estoque
    if product.Stock < quantity {
        return nil, fmt.Errorf("estoque insuficiente")
    }
    
    // 3️⃣ Reservar estoque (via interface)
    if err := uc.inventory.ReserveStock(productID, quantity); err != nil {
        return nil, fmt.Errorf("erro ao reservar estoque: %w", err)
    }
    
    // 4️⃣ Criar pedido (lógica de domínio)
    order := &Order{
        ID:       OrderID{Value: fmt.Sprintf("order_%d", time.Now().Unix())},
        Products: []Product{*product},
        Total:    product.Price * float64(quantity),
        Status:   "confirmed",
    }
    
    // 5️⃣ Salvar pedido (via interface)
    if err := uc.orderRepo.Save(order); err != nil {
        // 🔄 Rollback do estoque em caso de erro
        uc.inventory.ReleaseStock(productID, quantity)
        return nil, fmt.Errorf("erro ao salvar pedido: %w", err)
    }
    
    // 6️⃣ Enviar confirmação (via interface)
    if err := uc.emailService.SendOrderConfirmation(order); err != nil {
        // ⚠️ Log do erro, mas não falha o pedido
        fmt.Printf("Aviso: erro ao enviar email de confirmação: %v
", err)
    }
    
    return order, nil
}

// ========================================
// 🔌 INFRASTRUCTURE LAYER (Adapters)
// ========================================

// 💾 Adapter PostgreSQL (implementa ProductRepository)
type PostgreSQLProductRepo struct {
    connectionString string
}

func (r *PostgreSQLProductRepo) FindByID(id string) (*Product, error) {
    fmt.Printf("🗄️ [PostgreSQL] SELECT * FROM products WHERE id = '%s'
", id)
    // Simulação de busca no banco
    return &Product{
        ID:    id,
        Name:  "Produto " + id,
        Price: 99.99,
        Stock: 10,
    }, nil
}

func (r *PostgreSQLProductRepo) Save(product *Product) error {
    fmt.Printf("💾 [PostgreSQL] Salvando produto: %s
", product.Name)
    return nil
}

func (r *PostgreSQLProductRepo) UpdateStock(id string, newStock int) error {
    fmt.Printf("📦 [PostgreSQL] UPDATE products SET stock = %d WHERE id = '%s'
", newStock, id)
    return nil
}

// 📋 Adapter MongoDB (implementa OrderRepository)
type MongoOrderRepo struct {
    database string
}

func (r *MongoOrderRepo) Save(order *Order) error {
    fmt.Printf("🍃 [MongoDB] Salvando pedido %s no banco %s
", order.ID.Value, r.database)
    return nil
}

func (r *MongoOrderRepo) FindByID(orderID OrderID) (*Order, error) {
    fmt.Printf("🔍 [MongoDB] Buscando pedido %s
", orderID.Value)
    return nil, nil
}

// 📧 Adapter SendGrid (implementa EmailService)
type SendGridEmailService struct {
    apiKey string
}

func (s *SendGridEmailService) SendOrderConfirmation(order *Order) error {
    fmt.Printf("📧 [SendGrid] Enviando confirmação do pedido %s (Total: $%.2f)
", 
        order.ID.Value, order.Total)
    return nil
}

// 📦 Adapter Redis (implementa InventoryService)
type RedisInventoryService struct {
    redisAddr string
}

func (r *RedisInventoryService) ReserveStock(productID string, quantity int) error {
    fmt.Printf("🔒 [Redis] Reservando %d unidades do produto %s
", quantity, productID)
    return nil
}

func (r *RedisInventoryService) ReleaseStock(productID string, quantity int) error {
    fmt.Printf("🔓 [Redis] Liberando %d unidades do produto %s
", quantity, productID)
    return nil
}

// ========================================
// 🏭 DEPENDENCY INJECTION / MAIN
// ========================================

func main() {
    fmt.Println("🏛️ CLEAN ARCHITECTURE COM INTERFACES")
    fmt.Println("===================================")
    
    // 🔧 Criar adaptadores (camada de infraestrutura)
    productRepo := &PostgreSQLProductRepo{connectionString: "postgres://..."}
    orderRepo := &MongoOrderRepo{database: "orders_db"}
    emailService := &SendGridEmailService{apiKey: "sg_api_key"}
    inventoryService := &RedisInventoryService{redisAddr: "redis://localhost:6379"}
    
    // 🏭 Injetar dependências no use case (aplicação só conhece interfaces)
    processOrder := NewProcessOrderUseCase(
        productRepo,     // Interface ProductRepository
        orderRepo,       // Interface OrderRepository
        emailService,    // Interface EmailService
        inventoryService, // Interface InventoryService
    )
    
    // 🚀 Executar use case
    fmt.Println("
📦 Processando pedido...")
    order, err := processOrder.Execute("product_123", 2)
    
    if err != nil {
        fmt.Printf("❌ Erro: %v
", err)
        return
    }
    
    fmt.Printf("✅ Pedido processado com sucesso!
")
    fmt.Printf("   ID: %s
", order.ID.Value)
    fmt.Printf("   Total: $%.2f
", order.Total)
    fmt.Printf("   Status: %s
", order.Status)
    
    fmt.Println("
🎯 BENEFÍCIOS DESTA ARQUITETURA:")
    fmt.Println("• ✅ Use Case não depende de tecnologias específicas")
    fmt.Println("• 🔄 Fácil troca de implementações (PostgreSQL → MySQL)")
    fmt.Println("• 🧪 Testes simples com mocks")
    fmt.Println("• 📦 Camadas bem separadas")
    fmt.Println("• 🏛️ Regras de negócio isoladas na camada de domínio")
}

Bindings dinâmicos com interface

Go permite alternar contexto de execução usando interfaces. Basta o tipo seguir a assinatura desejada. O binding (ligação) é automático e dinâmico.

bindings_dinamicos.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

// 🎮 Interface para diferentes estratégias de jogo
type GameStrategy interface {
    PlayTurn() string
    GetDifficulty() string
}

// 😊 Estratégia Fácil
type EasyStrategy struct {
    playerName string
}

func (e EasyStrategy) PlayTurn() string {
    moves := []string{"Ataque básico", "Defesa simples", "Movimento cauteloso"}
    return moves[rand.Intn(len(moves))]
}

func (e EasyStrategy) GetDifficulty() string {
    return "Fácil"
}

// 🔥 Estratégia Difícil
type HardStrategy struct {
    playerName string
}

func (h HardStrategy) PlayTurn() string {
    moves := []string{"Combo devastador", "Contra-ataque perfeito", "Estratégia complexa"}
    return moves[rand.Intn(len(moves))]
}

func (h HardStrategy) GetDifficulty() string {
    return "Difícil"
}

// 🤖 Estratégia de IA
type AIStrategy struct {
    version string
}

func (ai AIStrategy) PlayTurn() string {
    return fmt.Sprintf("IA v%s calculou o movimento ótimo", ai.version)
}

func (ai AIStrategy) GetDifficulty() string {
    return "IA"
}

// 🎯 Contexto do jogo (usa interface)
type GameContext struct {
    strategy GameStrategy  // Binding dinâmico!
    round    int
}

func (g *GameContext) SetStrategy(strategy GameStrategy) {
    g.strategy = strategy  // 🔄 Troca estratégia dinamicamente
    fmt.Printf("🔄 Estratégia alterada para: %s
", strategy.GetDifficulty())
}

func (g *GameContext) PlayRound() {
    g.round++
    fmt.Printf("🎮 Round %d [%s]: %s
", 
        g.round, g.strategy.GetDifficulty(), g.strategy.PlayTurn())
}

// 🏭 Factory para criar estratégias dinamicamente
func CreateStrategy(level string) GameStrategy {
    switch level {
    case "easy":
        return EasyStrategy{playerName: "Iniciante"}
    case "hard":
        return HardStrategy{playerName: "Veterano"}
    case "ai":
        return AIStrategy{version: "2.0"}
    default:
        return EasyStrategy{playerName: "Padrão"}
    }
}

// 📊 Sistema de progressão que muda estratégia automaticamente
type ProgressionSystem struct {
    game    *GameContext
    score   int
    levels  []string
}

func (p *ProgressionSystem) UpdateDifficulty() {
    var newLevel string
    
    switch {
    case p.score < 100:
        newLevel = "easy"
    case p.score < 500:
        newLevel = "hard"
    default:
        newLevel = "ai"
    }
    
    // 🔄 Binding dinâmico baseado na pontuação
    newStrategy := CreateStrategy(newLevel)
    p.game.SetStrategy(newStrategy)
}

func (p *ProgressionSystem) AddScore(points int) {
    p.score += points
    fmt.Printf("📈 Pontuação: %d (+%d)
", p.score, points)
    p.UpdateDifficulty()
}

// 🔀 Demonstração de polimorfismo dinâmico
func DemonstratePolymorphism() {
    strategies := []GameStrategy{
        EasyStrategy{playerName: "João"},
        HardStrategy{playerName: "Maria"},
        AIStrategy{version: "3.0"},
    }
    
    fmt.Println("
🔀 POLIMORFISMO DINÂMICO:")
    for i, strategy := range strategies {
        fmt.Printf("Estratégia %d: %s - %s
", 
            i+1, strategy.GetDifficulty(), strategy.PlayTurn())
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())
    
    fmt.Println("🎮 DEMONSTRAÇÃO DE BINDINGS DINÂMICOS")
    fmt.Println("===================================")
    
    // 🎯 Criar contexto do jogo
    game := &GameContext{}
    
    // 🔄 Binding dinâmico: diferentes estratégias em runtime
    fmt.Println("
🔄 Alterando estratégias dinamicamente:")
    
    // Começar com estratégia fácil
    game.SetStrategy(CreateStrategy("easy"))
    game.PlayRound()
    
    // Mudar para difícil
    game.SetStrategy(CreateStrategy("hard"))
    game.PlayRound()
    
    // Mudar para IA
    game.SetStrategy(CreateStrategy("ai"))
    game.PlayRound()
    
    // 📊 Sistema de progressão automática
    fmt.Println("
📊 Sistema de progressão automática:")
    progression := &ProgressionSystem{
        game:   game,
        score:  0,
        levels: []string{"easy", "hard", "ai"},
    }
    
    // Simular progressão no jogo
    progression.AddScore(50)   // Ainda fácil
    progression.game.PlayRound()
    
    progression.AddScore(100)  // Agora difícil
    progression.game.PlayRound()
    
    progression.AddScore(400)  // Agora IA
    progression.game.PlayRound()
    
    // 🔀 Demonstrar polimorfismo
    DemonstratePolymorphism()
    
    fmt.Println("
🎯 CARACTERÍSTICAS DOS BINDINGS DINÂMICOS:")
    fmt.Println("• 🔄 Troca de implementação em tempo de execução")
    fmt.Println("• 🏭 Factory pattern para criação dinâmica")
    fmt.Println("• 📊 Adaptação baseada em estado/contexto")
    fmt.Println("• 🎮 Mesmo código funciona com diferentes comportamentos")
    fmt.Println("• ⚡ Performance mantida (binding é automático)")
}

Encapsulamento com interface

A implementação de métodos fica escondida do restante do software. O código depende apenas da interface, promovendo encapsulamento natural.

encapsulamento_interfaces.go
package main

import (
    "fmt"
    "log"
    "time"
)

// 🔐 Interface pública (contrato exposto)
type SecureWallet interface {
    GetBalance() float64
    Transfer(amount float64, destination string) error
    GetTransactionHistory() []string
}

// 🏦 Implementação privada (detalhes encapsulados)
type cryptoWallet struct {
    // 🔒 Campos privados (minúsculos) - não acessíveis externamente
    privateKey     string
    balance        float64
    transactions   []transaction
    encryptionKey  string
    securityLevel  int
}

// 📝 Estrutura interna privada
type transaction struct {
    id        string
    amount    float64
    timestamp time.Time
    toAddress string
    fromAddress string
}

// 🏭 Constructor (função pública que retorna interface)
func NewSecureWallet(initialBalance float64) SecureWallet {
    return &cryptoWallet{
        privateKey:    generatePrivateKey(),    // Função interna
        balance:       initialBalance,
        transactions:  make([]transaction, 0),
        encryptionKey: generateEncryptionKey(), // Função interna
        securityLevel: 5,
    }
}

// 🔐 Métodos PÚBLICOS da interface (contratos expostos)
func (w *cryptoWallet) GetBalance() float64 {
    // 🛡️ Validação de segurança interna (encapsulada)
    if !w.validateAccess() {
        log.Println("⚠️ Acesso negado: validação de segurança falhou")
        return 0
    }
    
    return w.balance // Cliente só vê o resultado, não como é calculado
}

func (w *cryptoWallet) Transfer(amount float64, destination string) error {
    // 🔍 Validações internas (encapsuladas)
    if !w.validateTransfer(amount) {
        return fmt.Errorf("transferência inválida")
    }
    
    if !w.hasEnoughBalance(amount) {
        return fmt.Errorf("saldo insuficiente")
    }
    
    // 🔐 Processamento criptográfico (encapsulado)
    encryptedTx := w.encryptTransaction(amount, destination)
    
    // 💾 Armazenamento seguro (encapsulado)
    w.storeTransaction(encryptedTx)
    
    // 💰 Atualização de saldo (encapsulada)
    w.updateBalance(-amount)
    
    return nil
}

func (w *cryptoWallet) GetTransactionHistory() []string {
    // 🔍 Cliente recebe dados formatados, não estrutura interna
    history := make([]string, len(w.transactions))
    
    for i, tx := range w.transactions {
        // 🎭 Formatar dados sem expor estrutura interna
        history[i] = w.formatTransaction(tx)
    }
    
    return history
}

// 🔒 Métodos PRIVADOS (implementação encapsulada)
func (w *cryptoWallet) validateAccess() bool {
    // Lógica complexa de segurança escondida
    return w.securityLevel >= 3
}

func (w *cryptoWallet) validateTransfer(amount float64) bool {
    return amount > 0 && amount <= 10000 // Limite interno
}

func (w *cryptoWallet) hasEnoughBalance(amount float64) bool {
    return w.balance >= amount
}

func (w *cryptoWallet) encryptTransaction(amount float64, destination string) transaction {
    // Criptografia complexa escondida do cliente
    return transaction{
        id:          generateTransactionID(),
        amount:      amount,
        timestamp:   time.Now(),
        toAddress:   destination,
        fromAddress: w.privateKey[0:8] + "...", // Endereço ofuscado
    }
}

func (w *cryptoWallet) storeTransaction(tx transaction) {
    w.transactions = append(w.transactions, tx)
}

func (w *cryptoWallet) updateBalance(delta float64) {
    w.balance += delta
    // Poderia ter log, auditoria, etc. - tudo encapsulado
}

func (w *cryptoWallet) formatTransaction(tx transaction) string {
    return fmt.Sprintf("[%s] %.2f para %s em %s", 
        tx.id, tx.amount, tx.toAddress, tx.timestamp.Format("15:04:05"))
}

// 🛠️ Funções utilitárias privadas (não expostas)
func generatePrivateKey() string {
    return "pk_" + fmt.Sprintf("%d", time.Now().UnixNano())
}

func generateEncryptionKey() string {
    return "ek_" + fmt.Sprintf("%d", time.Now().UnixNano())
}

func generateTransactionID() string {
    return fmt.Sprintf("tx_%d", time.Now().UnixNano())
}

// 🏛️ Serviço que usa a wallet (só conhece a interface)
type BankingService struct {
    wallets map[string]SecureWallet // Só interface, não implementação
}

func NewBankingService() *BankingService {
    return &BankingService{
        wallets: make(map[string]SecureWallet),
    }
}

func (b *BankingService) CreateAccount(userID string, initialBalance float64) {
    // 🎯 Cria wallet, mas só vê a interface
    wallet := NewSecureWallet(initialBalance)
    b.wallets[userID] = wallet
    
    fmt.Printf("✅ Conta criada para %s com saldo inicial: %.2f
", 
        userID, wallet.GetBalance())
}

func (b *BankingService) ProcessTransfer(fromUser, toUser string, amount float64) {
    fromWallet := b.wallets[fromUser]
    if fromWallet == nil {
        fmt.Printf("❌ Usuário %s não encontrado
", fromUser)
        return
    }
    
    // 🔐 Usa apenas métodos da interface (implementação escondida)
    err := fromWallet.Transfer(amount, toUser)
    if err != nil {
        fmt.Printf("❌ Transferência falhou: %v
", err)
        return
    }
    
    fmt.Printf("✅ Transferência de %.2f de %s para %s realizada
", 
        amount, fromUser, toUser)
}

func main() {
    fmt.Println("🔐 DEMONSTRAÇÃO DE ENCAPSULAMENTO COM INTERFACES")
    fmt.Println("===============================================")
    
    // 🏦 Criar serviço bancário
    bank := NewBankingService()
    
    // 👥 Criar contas
    bank.CreateAccount("alice", 1000.0)
    bank.CreateAccount("bob", 500.0)
    
    fmt.Println("
💰 Estado inicial:")
    aliceWallet := bank.wallets["alice"]
    bobWallet := bank.wallets["bob"]
    
    fmt.Printf("Alice: %.2f
", aliceWallet.GetBalance())
    fmt.Printf("Bob: %.2f
", bobWallet.GetBalance())
    
    // 💸 Realizar transferências
    fmt.Println("
💸 Transferências:")
    bank.ProcessTransfer("alice", "bob", 200.0)
    bank.ProcessTransfer("alice", "bob", 50.0)
    
    // 📊 Estado final
    fmt.Println("
📊 Estado final:")
    fmt.Printf("Alice: %.2f
", aliceWallet.GetBalance())
    fmt.Printf("Bob: %.2f
", bobWallet.GetBalance())
    
    // 📋 Histórico de transações
    fmt.Println("
📋 Histórico da Alice:")
    history := aliceWallet.GetTransactionHistory()
    for _, tx := range history {
        fmt.Printf("  %s
", tx)
    }
    
    fmt.Println("
🎯 BENEFÍCIOS DO ENCAPSULAMENTO:")
    fmt.Println("• 🔒 Implementação criptográfica escondida")
    fmt.Println("• 🛡️ Validações de segurança internas")
    fmt.Println("• 🏦 Cliente só vê interface limpa")
    fmt.Println("• 🔧 Implementação pode mudar sem afetar clientes")
    fmt.Println("• 📦 Complexidade interna isolada")
    fmt.Println("• 🎭 Dados formatados antes da exposição")
}

Mudanças para crescimento do projeto

Conforme o projeto cresce, o uso de interfaces permite adicionar novas formas, comportamentos e algoritmos sem afetar as dependências existentes.

Resumo das boas práticas

Defina interfaces pequenas, bem nomeadas, com comportamentos específicos. Não exponha métodos desnecessários. Use composição para combiná-las em casos complexos.

Checklist de Implementação

Criou structs com comportamentos distintos
Implementou métodos com receivers apropriados
Declarou interface com os métodos corretos
Usou interface como parâmetro em funções
Testou chamada polimórfica com tipos diferentes
Criou mocks para simular comportamentos
Validou erros na ausência de métodos obrigatórios

Domine React e Node com o CrazyStack

Aprenda técnicas avançadas de React com nosso curso completo