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
// Definir uma interface Falante
type Falante interface {
Falar() string
}
Implementação Implícita
// 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
}
implements. Contanto que um tipo tenha todos os métodos exigidos por uma interface, ele automaticamente implementa essa interface.
Usando Interfaces
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{}
// 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
}
interface{} pode ser abreviado para any. São equivalentes.
Asserções de Tipo e Switch de Tipo
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)
}
}
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
// 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
io.Reader, io.Writer, fmt.Stringer, etc.
Exemplos
Exemplo: Cálculo de Área de Formas (Dificuldade ⭐)
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)
}
Retangulo -> Area: 50.00, Perimetro: 30.00
Circulo -> Area: 153.94, Perimetro: 43.98
Exemplo: Slices de Interfaces e Ordenação (Dificuldade ⭐⭐)
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))
}
=== 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 ⭐⭐⭐)
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())
}
=== 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)
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
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 |
❓ 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?
- Quando você precisa de polimorfismo (mesma função lida com diferentes tipos)
- Quando você precisa de desacoplamento (depender de abstrações, não de implementações concretas)
- Quando você precisa de testes com mock (usar interfaces para substituir dependências reais)
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.
// 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:
// 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
// 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.



