Interfaces

Lição 9: Interfaces

Analogia do Mundo Real

Imagine que você vai a um restaurante para pedir comida. Você não precisa saber quem é o chef, qual panela ele usa, ou como ele cozinha — você apenas olha o cardápio e pede "Frango Xinxim." O cardápio é uma interface: ele define "o que pode ser feito" sem se importar com "quem faz" ou "como é feito."

Em Go, interfaces funcionam da mesma maneira. Elas definem um conjunto de assinaturas de métodos, e qualquer tipo que implementa esses métodos automaticamente satisfaz a interface — sem necessidade de declaração explícita.


Conceitos Principais

Conceito Descrição
Interface Uma coleção de assinaturas de métodos que define um contrato comportamental
Implementação Implícita Um tipo automaticamente satisfaz uma interface se implementa todos os métodos da interface
Duck Typing "Se anda como um pato e grasna como um pato, então é um pato"
Interface Vazia interface{} Não contém métodos; qualquer tipo a satisfaz
Asserção de Tipo Extrai um tipo concreto de um valor de interface
Composição de Interfaces Construir interfaces maiores embutindo múltiplas interfaces

Sintaxe e Uso Básico

Definindo uma Interface

GO
// Definir uma interface Falante
type Falante interface {
    Falar() string
}

Implementação Implícita

GO
// Tipo Cachorro implementa a interface Falante (sem declaração necessária)
type Cachorro struct {
    Nome string
}

func (c Cachorro) Falar() string {
    return "Au! Eu sou " + c.Nome
}

// Tipo Gato também implementa a interface Falante
type Gato struct {
    Nome string
}

func (g Gato) Falar() string {
    return "Miau! Eu sou " + g.Nome
}
💡 Dica: Go não tem palavra-chave implements. Contanto que um tipo tenha todos os métodos exigidos por uma interface, ele automaticamente implementa essa interface.

Usando Interfaces

GO
func fazerFalar(f Falante) {
    fmt.Println(f.Falar())
}

func main() {
    cachorro := Cachorro{Nome: "Rex"}
    gato := Gato{Nome: "Bigodes"}

    fazerFalar(cachorro) // Saída: Au! Eu sou Rex
    fazerFalar(gato)     // Saída: Miau! Eu sou Bigodes
}

Interface Vazia interface{}

GO
// Interface vazia pode armazenar valores de qualquer tipo
func imprimirQualquerCoisa(v interface{}) {
    fmt.Printf("Valor: %v, Tipo: %T\n", v, v)
}

func main() {
    imprimirQualquerCoisa(42)         // Valor: 42, Tipo: int
    imprimirQualquerCoisa("ola")      // Valor: ola, Tipo: string
    imprimirQualquerCoisa(3.14)       // Valor: 3.14, Tipo: float64
}
💡 Dica: No Go 1.18+, interface{} pode ser abreviado para any. São equivalentes.

Asserções de Tipo e Switch de Tipo

GO
func descrever(v interface{}) {
    // Asserção de tipo: tentar converter o valor de interface para um tipo concreto
    str, ok := v.(string)
    if ok {
        fmt.Println("Isso é uma string:", str)
        return
    }

    // Switch de tipo: lidar elegantemente com múltiplos tipos
    switch val := v.(type) {
    case int:
        fmt.Println("Isso é um inteiro:", val)
    case float64:
        fmt.Println("Isso é um float:", val)
    case bool:
        fmt.Println("Isso é um booleano:", val)
    default:
        fmt.Printf("Tipo desconhecido: %T\n", val)
    }
}
💡 Dica: Usar o padrão "vírgula ok" com asserções de tipo evita panic. v.(Tipo) entra em panic se a asserção falhar, enquanto v, ok := v.(Tipo) retorna seguramente um valor zero e false.

Composição de Interfaces

GO
// Interfaces base
type Leitor interface {
    Read(p []byte) (n int, err error)
}

type Escritor interface {
    Write(p []byte) (n int, err error)
}

// Interface composta: embute múltiplas interfaces
type LeitorEscritor interface {
    Leitor
    Escritor
}

// LeitorEscritor requer que ambos os métodos Read e Write sejam implementados
💡 Dica: Composição de interfaces segue o princípio de "interface pequena". Muitas interfaces na biblioteca padrão do Go têm apenas 1-2 métodos, como io.Reader, io.Writer, fmt.Stringer, etc.


Exemplos

Exemplo: Cálculo de Área de Formas (Dificuldade ⭐)

GO
package main

import (
    "fmt"
    "math"
)

// Interface Forma define o comportamento de "formas"
type Forma interface {
    Area() float64
    Perimetro() float64
}

// Retangulo
type Retangulo struct {
    Largura float64
    Altura  float64
}

func (r Retangulo) Area() float64 {
    return r.Largura * r.Altura
}

func (r Retangulo) Perimetro() float64 {
    return 2 * (r.Largura + r.Altura)
}

// Circulo
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
}

// imprimirInfoForma aceita qualquer implementação da interface Forma
func imprimirInfoForma(f Forma) {
    fmt.Printf("Area: %.2f, Perimetro: %.2f\n", f.Area(), f.Perimetro())
}

func main() {
    ret := Retangulo{Largura: 10, Altura: 5}
    circ := Circulo{Raio: 7}

    fmt.Print("Retangulo -> ")
    imprimirInfoForma(ret)

    fmt.Print("Circulo -> ")
    imprimirInfoForma(circ)
}
▶ Experimente
Retangulo -> Area: 50.00, Perimetro: 30.00
Circulo -> Area: 153.94, Perimetro: 43.98

Exemplo: Slices de Interfaces e Ordenação (Dificuldade ⭐⭐)

GO
package main

import (
    "fmt"
    "sort"
)

// Interface Funcionario
type Funcionario interface {
    Nome() string
    Salario() float64
}

// TempoIntegral funcionário de tempo integral
type TempoIntegral struct {
    nome   string
    anual  float64 // Salário anual
}

func (f TempoIntegral) Nome() string    { return f.nome }
func (f TempoIntegral) Salario() float64 { return f.anual }

// Contratado trabalhador contratado
type Contratado struct {
    nome    string
    porHora float64 // Valor por hora
    horas   float64 // Horas trabalhadas
}

func (c Contratado) Nome() string    { return c.nome }
func (c Contratado) Salario() float64 { return c.porHora * c.horas }

// PorSalario implementa sort.Interface, ordena por salário
type PorSalario []Funcionario

func (s PorSalario) Len() int           { return len(s) }
func (s PorSalario) Less(i, j int) bool { return s[i].Salario() < s[j].Salario() }
func (s PorSalario) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// custoTotal calcula o custo total de mão de obra
func custoTotal(funcionarios []Funcionario) float64 {
    total := 0.0
    for _, f := range funcionarios {
        total += f.Salario()
    }
    return total
}

func main() {
    equipe := []Funcionario{
        TempoIntegral{nome: "João Silva", anual: 120000},
        Contratado{nome: "Maria Santos", porHora: 200, horas: 1000},
        TempoIntegral{nome: "Pedro Oliveira", anual: 150000},
        Contratado{nome: "Ana Costa", porHora: 180, horas: 800},
    }

    fmt.Println("=== Antes da Ordenação por Salário ===")
    for _, f := range equipe {
        fmt.Printf("  %s: $%.0f\n", f.Nome(), f.Salario())
    }

    sort.Sort(PorSalario(equipe))

    fmt.Println("\n=== Após Ordenação por Salário ===")
    for _, f := range equipe {
        fmt.Printf("  %s: $%.0f\n", f.Nome(), f.Salario())
    }

    fmt.Printf("\nCusto total de mão de obra: $%.0f\n", custoTotal(equipe))
}
▶ Experimente
=== Antes da Ordenação por Salário ===
  João Silva: $120000
  Maria Santos: $200000
  Pedro Oliveira: $150000
  Ana Costa: $144000

=== Após Ordenação por Salário ===
  João Silva: $120000
  Ana Costa: $144000
  Pedro Oliveira: $150000
  Maria Santos: $200000

Custo total de mão de obra: $614000

Exemplo: Implementando Interfaces io.Reader/Writer (Dificuldade ⭐⭐⭐)

GO
package main

import (
    "fmt"
    "io"
    "strings"
)

// LeitorMaiusculo converte conteúdo lido para maiúsculas
type LeitorMaiusculo struct {
    fonte io.Reader
}

// Implementa a interface io.Reader
func (l *LeitorMaiusculo) Read(p []byte) (n int, err error) {
    n, err = l.fonte.Read(p)
    // Converter todos os bytes lidos para maiúsculas
    for i := 0; i < n; i++ {
        if p[i] >= 'a' && p[i] <= 'z' {
            p[i] = p[i] - 32 // ASCII: minúscula para maiúscula
        }
    }
    return
}

// Construtor LeitorMaiusculo
func NovoLeitorMaiusculo(r io.Reader) *LeitorMaiusculo {
    return &LeitorMaiusculo{fonte: r}
}

// EscritorPrefixo adiciona um prefixo antes de cada escrita
type EscritorPrefixo struct {
    prefixo string
    alvo    io.Writer
}

// Implementa a interface io.Writer
func (e *EscritorPrefixo) Write(dados []byte) (n int, err error) {
    // Escrever o prefixo primeiro
    _, err = e.alvo.Write([]byte(e.prefixo))
    if err != nil {
        return 0, err
    }
    // Depois escrever os dados reais
    return e.alvo.Write(dados)
}

// Construtor EscritorPrefixo
func NovoEscritorPrefixo(prefixo string, w io.Writer) *EscritorPrefixo {
    return &EscritorPrefixo{prefixo: prefixo, alvo: w}
}

// LeitorTee lê e escreve simultaneamente (similar ao comando tee)
func LeitorTee(r io.Reader, w io.Writer) io.Reader {
    return &leitorTee{r: r, w: w}
}

type leitorTee struct {
    r io.Reader
    w io.Writer
}

func (t *leitorTee) Read(p []byte) (n int, err error) {
    n, err = t.r.Read(p)
    if n > 0 {
        // Escrever em w enquanto lê
        t.w.Write(p[:n])
    }
    return
}

func main() {
    fmt.Println("=== Exemplo LeitorMaiusculo ===")
    // Criar um Reader a partir de uma string
    fonte := strings.NewReader("ola, interfaces go!")
    maiusculo := NovoLeitorMaiusculo(fonte)

    // Usar io.ReadAll para ler todo o conteúdo
    buf := make([]byte, 64)
    n, _ := maiusculo.Read(buf)
    fmt.Printf("Resultado maiúsculo: %s\n", string(buf[:n]))

    fmt.Println("\n=== Exemplo EscritorPrefixo ===")
    // Escrever para stdout com prefixo
    escritor := NovoEscritorPrefixo("[LOG] ", &strings.Builder{})
    escritor.Write([]byte("Sistema iniciado\n"))
    // Usar strings.Builder para capturar saída
    var builder strings.Builder
    ep := NovoEscritorPrefixo("[DEBUG] ", &builder)
    ep.Write([]byte("Interface inicializada"))
    fmt.Println(builder.String())

    fmt.Println("\n=== Exemplo LeitorTee ===")
    // Ler e simultaneamente escrever para outro Writer
    entrada := strings.NewReader("Go é poderoso")
    var captura strings.Builder
    tee := LeitorTee(entrada, &captura)

    buf2 := make([]byte, 1024)
    n2, _ := tee.Read(buf2)
    fmt.Printf("Lido: %s\n", string(buf2[:n2]))
    fmt.Printf("Também capturado: %s\n", captura.String())
}
▶ Experimente
=== Exemplo LeitorMaiusculo ===
Resultado maiúsculo: OLA, INTERFACES GO!

=== Exemplo EscritorPrefixo ===
[DEBUG] Interface inicializada

=== Exemplo LeitorTee ===
Lido: Go é poderoso
Também capturado: Go é poderoso

Cenários de Aplicação do Mundo Real

Cenário 1: Sistema de Logging (Padrão Estratégia)

GO
package main

import (
    "fmt"
    "os"
    "time"
)

// Logger interface de logging
type Logger interface {
    Log(mensagem string)
}

// LoggerConsole logger de console
type LoggerConsole struct{}

func (c LoggerConsole) Log(mensagem string) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    fmt.Printf("[%s] %s\n", timestamp, mensagem)
}

// LoggerArquivo logger de arquivo
type LoggerArquivo struct {
    arquivo *os.File
}

func NovoLoggerArquivo(nomeArquivo string) (*LoggerArquivo, error) {
    f, err := os.OpenFile(nomeArquivo, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, err
    }
    return &LoggerArquivo{arquivo: f}, nil
}

func (f *LoggerArquivo) Log(mensagem string) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    fmt.Fprintf(f.arquivo, "[%s] %s\n", timestamp, mensagem)
}

// MultiLogger saída para múltiplos loggers simultaneamente
type MultiLogger struct {
    loggers []Logger
}

func (m *MultiLogger) Adicionar(l Logger) {
    m.loggers = append(m.loggers, l)
}

func (m *MultiLogger) Log(mensagem string) {
    for _, l := range m.loggers {
        l.Log(mensagem)
    }
}

// App usa a interface Logger, não se importa com a implementação específica
type App struct {
    logger Logger
}

func (a *App) Executar() {
    a.logger.Log("Aplicação iniciada")
    a.logger.Log("Processando requisição...")
    a.logger.Log("Processamento de requisição concluído")
}

func main() {
    // Combinar múltiplas saídas de log
    multi := &MultiLogger{}
    multi.Adicionar(LoggerConsole{})

    // Pode facilmente trocar ou adicionar métodos de saída de log
    app := &App{logger: multi}
    app.Executar()
}
[2026-06-26 10:30:00] Aplicação iniciada
[2026-06-26 10:30:00] Processando requisição...
[2026-06-26 10:30:00] Processamento de requisição concluído

Cenário 2: Camada de Abstração de Armazenamento de Dados

GO
package main

import "fmt"

// Loja interface de armazenamento
type Loja interface {
    Obter(chave string) (string, bool)
    Definir(chave string, valor string)
    Deletar(chave string)
    Chaves() []string
}

// LojaMemoria implementação de armazenamento em memória
type LojaMemoria struct {
    dados map[string]string
}

func NovaLojaMemoria() *LojaMemoria {
    return &LojaMemoria{dados: make(map[string]string)}
}

func (m *LojaMemoria) Obter(chave string) (string, bool) {
    val, ok := m.dados[chave]
    return val, ok
}

func (m *LojaMemoria) Definir(chave string, valor string) {
    m.dados[chave] = valor
}

func (m *LojaMemoria) Deletar(chave string) {
    delete(m.dados, chave)
}

func (m *LojaMemoria) Chaves() []string {
    chaves := make([]string, 0, len(m.dados))
    for k := range m.dados {
        chaves = append(chaves, k)
    }
    return chaves
}

// ServicoCache usa a interface Loja, desacoplado do armazenamento específico
type ServicoCache struct {
    loja Loja
}

func (c *ServicoCache) ObterOuDefinir(chave, valorPadrao string) string {
    if val, ok := c.loja.Obter(chave); ok {
        return val
    }
    c.loja.Definir(chave, valorPadrao)
    return valorPadrao
}

func (c *ServicoCache) ObterTodos() map[string]string {
    resultado := make(map[string]string)
    for _, chave := range c.loja.Chaves() {
        if val, ok := c.loja.Obter(chave); ok {
            resultado[chave] = val
        }
    }
    return resultado
}

func main() {
    // Usar armazenamento em memória
    loja := NovaLojaMemoria()
    cache := &ServicoCache{loja: loja}

    // Escrever dados
    cache.ObterOuDefinir("usuario:1", "Alice")
    cache.ObterOuDefinir("usuario:2", "Bob")
    cache.ObterOuDefinir("config:tema", "escuro")

    // Ler dados
    fmt.Println("Todos os dados em cache:")
    for k, v := range cache.ObterTodos() {
        fmt.Printf("  %s = %s\n", k, v)
    }

    // Testar ObterOuDefinir: chave existente retorna valor antigo
    resultado := cache.ObterOuDefinir("usuario:1", "Charlie")
    fmt.Printf("\nValor de usuario:1: %s\n", resultado)
}
Todos os dados em cache:
  usuario:1 = Alice
  usuario:2 = Bob
  config:tema = escuro

Valor de usuario:1: Alice

📖 Resumo

Ponto Chave Descrição
Interfaces definem comportamento Importa apenas "o que pode fazer", não "o que é"
Implementação implícita Sem declaração necessária; implementar os métodos satisfaz a interface
Interface vazia any Pode armazenar valores de qualquer tipo
Asserção de tipo Extrair tipos concretos de valores de interface; use o padrão vírgula ok para segurança
Composição de interfaces Construir interfaces grandes embutindo pequenas
Programar para interfaces Depender de interfaces em vez de implementações concretas para flexibilidade
💡 Melhor Prática: Go favorece interfaces pequenas. As interfaces mais usadas na biblioteca padrão geralmente têm apenas 1-2 métodos. Ao definir interfaces, comece pelas necessidades do consumidor (chamador), não do implementador.


❓ Perguntas Frequentes

P1: Qual é a diferença entre uma interface e uma struct?

Uma struct é um tipo de dados concreto que define "o que é"; uma interface é um contrato comportamental que define "o que pode fazer." Structs podem ser instanciadas; interfaces não podem ser instanciadas diretamente, mas podem armazenar valores de qualquer tipo que implemente a interface.

P2: Por que Go não precisa de uma palavra-chave implements?

Go usa design de duck typing. O compilador verifica automaticamente em tempo de compilação se um tipo satisfaz uma interface. Este design desacopla completamente interfaces de implementações — você pode definir novas interfaces para tipos de bibliotecas de terceiros sem modificar código existente.

P3: Quando devo definir uma interface?

💡 Regra prática: Escreva implementações concretas primeiro, depois defina interfaces quando descobrir a necessidade de abstração. Não sobre-projete.

P4: Qual é a diferença entre interface{} e any?

Nenhuma. any é um apelido de tipo para interface{} introduzido no Go 1.18. São completamente equivalentes. any é recomendado por ser mais conciso.


📝 Exercícios

Exercício 1: Básicos — Implementar a Interface Stringer

A interface fmt.Stringer tem apenas um método: String() string. Ao usar fmt.Println ou formatação %v, Go automaticamente chama este método.

GO
// Implementar a interface fmt.Stringer para os seguintes tipos
type Temperatura struct {
    Celsius float64
}

type Dinheiro struct {
    Valor    float64
    Moeda    string
}

// Comportamento esperado:
// fmt.Println(Temperatura{36.5})  -> "36.5°C"
// fmt.Println(Dinheiro{99.9, "BRL"}) -> "R$99.90"

Exercício 2: Intermediário — Projetar um Sistema de Notificações

Projetar um sistema de notificações que suporte múltiplos métodos de notificação:

GO
// 1. Definir uma interface Notificador
// 2. Implementar NotificadorEmail, NotificadorSMS, NotificadorWhatsApp
// 3. Implementar uma função que possa enviar para múltiplos Notificadores simultaneamente
// 4. Usar um slice de interfaces para armazenar diferentes Notificadores

Exercício 3: Desafio — Implementar um Sistema de Plugins Simples

GO
// Definir uma interface Plugin com métodos Nome(), Versao(), Executar()
// Implementar pelo menos 3 Plugins diferentes
// Criar um GerenciadorPlugins que possa registrar, encontrar e executar plugins
// Dica: Usar map[string]Plugin para armazenar plugins

Próxima Lição

Interfaces são a pedra angular do polimorfismo em Go. Com interfaces dominadas, você pode escrever código flexível, testável e extível. Em seguida, aprenderemos sobre o mecanismo de tratamento de erros do Go — uma das filosofias de design mais importantes da linguagem Go.

👉 Próxima Lição: Tratamento de Erros

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%