Go

Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo

13 min de leitura

Guia Completo de Interfaces em Go: Definição, Implementação Implícita e Polimorfismo

Entendendo Interfaces em Go Interfaces em Go são um dos conceitos mais poderosos e elegantes da linguagem. Diferente de muitas linguagens orientadas a objetos, Go não utiliza herança de classes. Em vez disso, usa composição e interfaces para criar código flexível e desacoplado. Uma interface é um contrato que define um conjunto de métodos que um tipo deve implementar. Se um tipo implementa todos os métodos da interface, ele satisfaz automaticamente aquela interface — sem necessidade de declaração explícita. Essa abordagem, chamada de "implementação implícita", reduz acoplamento e torna o código mais testável e manutenível. Para compreender interfaces em Go, você precisa abandonar o pensamento tradicional de linguagens como Java ou C#, onde você declara explicitamente que uma classe implementa uma interface. Em Go, não há essa palavra-chave. Se caminha como um pato, nada como um pato e grasna como um pato, então é um pato — isso é polimorfismo em Go. Definição e Sintaxe de Interfaces Declarando uma Interface

<h2>Entendendo Interfaces em Go</h2>

<p>Interfaces em Go são um dos conceitos mais poderosos e elegantes da linguagem. Diferente de muitas linguagens orientadas a objetos, Go não utiliza herança de classes. Em vez disso, usa composição e interfaces para criar código flexível e desacoplado. Uma interface é um contrato que define um conjunto de métodos que um tipo deve implementar. Se um tipo implementa todos os métodos da interface, ele satisfaz automaticamente aquela interface — sem necessidade de declaração explícita. Essa abordagem, chamada de &quot;implementação implícita&quot;, reduz acoplamento e torna o código mais testável e manutenível.</p>

<p>Para compreender interfaces em Go, você precisa abandonar o pensamento tradicional de linguagens como Java ou C#, onde você declara explicitamente que uma classe implementa uma interface. Em Go, não há essa palavra-chave. Se caminha como um pato, nada como um pato e grasna como um pato, então é um pato — isso é polimorfismo em Go.</p>

<h2>Definição e Sintaxe de Interfaces</h2>

<h3>Declarando uma Interface</h3>

<p>Uma interface em Go é declarada usando a palavra-chave <code>type</code> seguida do nome e a palavra-chave <code>interface</code>. Dentro das chaves, você lista as assinaturas dos métodos que devem ser implementados.</p>

<pre><code class="language-go">package main

type Veiculo interface {

Acelerar()

Frear()

Velocidade() int

}</code></pre>

<p>Essa interface <code>Veiculo</code> define que qualquer tipo que quiser ser um <code>Veiculo</code> deve implementar três métodos: <code>Acelerar()</code>, <code>Frear()</code> e <code>Velocidade()</code> que retorna um <code>int</code>. Note que não há corpo dos métodos na definição da interface — apenas as assinaturas.</p>

<h3>Regras Importantes sobre Interfaces</h3>

<p>Interfaces em Go podem conter apenas assinaturas de métodos. Elas não podem ter campos de dados ou constantes. Uma interface pode também estar vazia (<code>interface{}</code>), o que significa que qualquer tipo implementa essa interface — útil para código genérico. As interfaces também podem embutir outras interfaces, criando composição de contratos.</p>

<pre><code class="language-go">package main

type Animal interface {

Fazer_Som()

}

type Terrestre interface {

Animal

Caminhar()

}</code></pre>

<p>Aqui, <code>Terrestre</code> herda implicitamente o método <code>Fazer_Som</code> de <code>Animal</code>, além de exigir <code>Caminhar()</code>. Um tipo que implementa <code>Terrestre</code> precisa implementar ambos os métodos.</p>

<h2>Implementação Implícita: Satisfazendo Interfaces</h2>

<h3>Como Funciona a Satisfação Implícita</h3>

<p>Um tipo satisfaz uma interface simplesmente implementando todos os seus métodos. Não é necessário escrever <code>implements Veiculo</code> ou qualquer coisa parecida. Go verifica automaticamente se um tipo possui todos os métodos requeridos.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

type Veiculo interface {

Acelerar()

Frear()

Velocidade() int

}

type Carro struct {

velocidadeAtual int

}

// Implementar o método Acelerar

func (c *Carro) Acelerar() {

c.velocidadeAtual += 20

fmt.Println(&quot;Carro acelerou! Velocidade:&quot;, c.velocidadeAtual)

}

// Implementar o método Frear

func (c *Carro) Frear() {

c.velocidadeAtual -= 10

if c.velocidadeAtual &lt; 0 {

c.velocidadeAtual = 0

}

fmt.Println(&quot;Carro freou! Velocidade:&quot;, c.velocidadeAtual)

}

// Implementar o método Velocidade

func (c *Carro) Velocidade() int {

return c.velocidadeAtual

}

type Bicicleta struct {

velocidadeAtual int

}

func (b *Bicicleta) Acelerar() {

b.velocidadeAtual += 5

fmt.Println(&quot;Bicicleta acelerou! Velocidade:&quot;, b.velocidadeAtual)

}

func (b *Bicicleta) Frear() {

b.velocidadeAtual -= 2

if b.velocidadeAtual &lt; 0 {

b.velocidadeAtual = 0

}

fmt.Println(&quot;Bicicleta freou! Velocidade:&quot;, b.velocidadeAtual)

}

func (b *Bicicleta) Velocidade() int {

return b.velocidadeAtual

}

func main() {

var v Veiculo

// Carro implementa Veiculo

carro := &amp;Carro{}

v = carro

v.Acelerar()

v.Frear()

fmt.Println(&quot;Velocidade do carro:&quot;, v.Velocidade())

// Bicicleta também implementa Veiculo

bicicleta := &amp;Bicicleta{}

v = bicicleta

v.Acelerar()

v.Frear()

fmt.Println(&quot;Velocidade da bicicleta:&quot;, v.Velocidade())

}</code></pre>

<p>Note que nem <code>Carro</code> nem <code>Bicicleta</code> declaram explicitamente que implementam <code>Veiculo</code>. Ambos são automaticamente considerados <code>Veiculo</code> porque possuem todos os métodos requeridos. Isso é a beleza da implementação implícita — você pode escrever código que trabalha com a interface <code>Veiculo</code> sem saber de antemão quais tipos concretos a implementarão.</p>

<h3>Recebedores de Método e Interfaces</h3>

<p>Um detalhe crucial: ao implementar métodos de uma interface, o tipo do recebedor importa. Se a interface exigir métodos com recebedor de ponteiro (como <code>func (c *Carro) Acelerar()</code>), você não pode usar um recebedor de valor (<code>func (c Carro) Acelerar()</code>). Go verifica isso rigorosamente.</p>

<pre><code class="language-go">type Imprimivel interface {

Imprimir()

}

type Documento struct {

conteudo string

}

// CORRETO: recebedor de ponteiro

func (d *Documento) Imprimir() {

fmt.Println(d.conteudo)

}

// Agora Documento satisfaz Imprimivel

var doc Imprimivel = &amp;Documento{conteudo: &quot;Hello&quot;}

doc.Imprimir() // Funciona</code></pre>

<h2>Polimorfismo em Go</h2>

<h3>O que é Polimorfismo e Como Go o Implementa</h3>

<p>Polimorfismo é a capacidade de um objeto responder a mensagens de diferentes formas. Em Go, polimorfismo é alcançado através de interfaces. Você escreve código genérico que trabalha com uma interface, e em tempo de execução, a implementação concreta é determinada pelo tipo real do objeto. Isso é chamado de &quot;polimorfismo de tempo de execução&quot; ou &quot;dispatch dinâmico&quot;.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

type Pagador interface {

Pagar(valor float64)

}

type CartaoCredito struct {

numero string

}

func (c *CartaoCredito) Pagar(valor float64) {

fmt.Printf(&quot;Pagamento de R$ %.2f processado com cartão %s\n&quot;, valor, c.numero)

}

type PayPal struct {

email string

}

func (p *PayPal) Pagar(valor float64) {

fmt.Printf(&quot;Pagamento de R$ %.2f enviado para PayPal (%s)\n&quot;, valor, p.email)

}

type Criptomoeda struct {

carteira string

}

func (k *Criptomoeda) Pagar(valor float64) {

fmt.Printf(&quot;Pagamento de R$ %.2f realizado em blockchain (%s)\n&quot;, valor, k.carteira)

}

// Função polimórfica: aceita qualquer Pagador

func ProcessarPagamento(pagador Pagador, valor float64) {

pagador.Pagar(valor)

}

func main() {

// Mesmo código, diferentes comportamentos

cartao := &amp;CartaoCredito{numero: &quot;1234-5678-9012-3456&quot;}

paypal := &amp;PayPal{email: &quot;user@example.com&quot;}

crypto := &amp;Criptomoeda{carteira: &quot;0x123abc&quot;}

ProcessarPagamento(cartao, 100.00)

ProcessarPagamento(paypal, 50.00)

ProcessarPagamento(crypto, 75.50)

}</code></pre>

<p>Saída esperada:</p>

<pre><code>Pagamento de R$ 100.00 processado com cartão 1234-5678-9012-3456

Pagamento de R$ 50.00 enviado para PayPal (user@example.com)

Pagamento de R$ 75.50 realizado em blockchain (0x123abc)</code></pre>

<h3>Type Assertion e Type Switch</h3>

<p>Às vezes você precisa descobrir qual tipo concreto está por trás de uma interface. Go fornece <code>type assertion</code> para isso. Uma <code>type assertion</code> permite acessar o valor concreto de uma interface e verificar seu tipo.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

type Animal interface {

Som() string

}

type Cachorro struct{}

func (c *Cachorro) Som() string {

return &quot;Au au&quot;

}

type Gato struct{}

func (g *Gato) Som() string {

return &quot;Miau&quot;

}

func DescreverAnimal(a Animal) {

// Type assertion básica

if cachorro, ok := a.(*Cachorro); ok {

fmt.Println(&quot;É um cachorro:&quot;, cachorro.Som())

}

// Type switch para múltiplos tipos

switch animal := a.(type) {

case *Cachorro:

fmt.Println(&quot;Cachorro fazendo:&quot;, animal.Som())

case *Gato:

fmt.Println(&quot;Gato fazendo:&quot;, animal.Som())

default:

fmt.Println(&quot;Animal desconhecido:&quot;, a.Som())

}

}

func main() {

DescreverAnimal(&amp;Cachorro{})

DescreverAnimal(&amp;Gato{})

}</code></pre>

<p>A sintaxe <code>a.(*Cachorro)</code> tenta converter a interface para um ponteiro de <code>Cachorro</code>. Se bem-sucedida, retorna o valor e <code>true</code>; caso contrário, retorna <code>nil</code> e <code>false</code>. O <code>type switch</code> é similar a um switch tradicional, mas funciona com tipos em vez de valores.</p>

<h3>Polimorfismo com Interface Vazia</h3>

<p>A interface vazia <code>interface{}</code> é implementada por todos os tipos. Ela é útil quando você precisa trabalhar com valores de qualquer tipo, como em funções genéricas.</p>

<pre><code class="language-go">package main

import &quot;fmt&quot;

func Imprimir(valores ...interface{}) {

for _, v := range valores {

fmt.Println(v)

}

}

func ExtrairInfo(valor interface{}) {

switch v := valor.(type) {

case int:

fmt.Printf(&quot;Inteiro: %d\n&quot;, v)

case string:

fmt.Printf(&quot;String: %s\n&quot;, v)

case bool:

fmt.Printf(&quot;Booleano: %v\n&quot;, v)

default:

fmt.Printf(&quot;Tipo desconhecido: %T\n&quot;, v)

}

}

func main() {

Imprimir(42, &quot;Hello&quot;, true, 3.14)

ExtrairInfo(100)

ExtrairInfo(&quot;Go é incrível&quot;)

}</code></pre>

<p>Use <code>interface{}</code> com cuidado, pois reduz a segurança de tipos. Go é uma linguagem tipada, e abrir mão disso frequentemente leva a código frágil.</p>

<h2>Casos de Uso Práticos e Boas Práticas</h2>

<h3>Exemplo: Sistema de Logging</h3>

<p>Um caso de uso clássico de interfaces e polimorfismo é um sistema de logging extensível. Você define uma interface <code>Logger</code> e múltiplas implementações, permitindo trocar o mecanismo de logging sem alterar o código cliente.</p>

<pre><code class="language-go">package main

import (

&quot;fmt&quot;

&quot;log&quot;

&quot;os&quot;

)

type Logger interface {

Info(mensagem string)

Erro(mensagem string)

Debug(mensagem string)

}

type LoggerConsole struct{}

func (l *LoggerConsole) Info(mensagem string) {

fmt.Println(&quot;[INFO]&quot;, mensagem)

}

func (l *LoggerConsole) Erro(mensagem string) {

fmt.Println(&quot;[ERRO]&quot;, mensagem)

}

func (l *LoggerConsole) Debug(mensagem string) {

fmt.Println(&quot;[DEBUG]&quot;, mensagem)

}

type LoggerArquivo struct {

arquivo *os.File

}

func NovoLoggerArquivo(caminho string) (*LoggerArquivo, error) {

arquivo, err := os.OpenFile(caminho, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

if err != nil {

return nil, err

}

return &amp;LoggerArquivo{arquivo: arquivo}, nil

}

func (l *LoggerArquivo) Info(mensagem string) {

l.arquivo.WriteString(&quot;[INFO] &quot; + mensagem + &quot;\n&quot;)

}

func (l *LoggerArquivo) Erro(mensagem string) {

l.arquivo.WriteString(&quot;[ERRO] &quot; + mensagem + &quot;\n&quot;)

}

func (l *LoggerArquivo) Debug(mensagem string) {

l.arquivo.WriteString(&quot;[DEBUG] &quot; + mensagem + &quot;\n&quot;)

}

// Função que usa Logger sem saber qual implementação é

func ProcessarDados(logger Logger, dados string) {

logger.Info(&quot;Iniciando processamento de: &quot; + dados)

logger.Debug(&quot;Processando detalhes...&quot;)

logger.Info(&quot;Processamento concluído&quot;)

}

func main() {

// Usando LoggerConsole

console := &amp;LoggerConsole{}

ProcessarDados(console, &quot;arquivo.txt&quot;)

// Usando LoggerArquivo

arquivo, err := NovoLoggerArquivo(&quot;log.txt&quot;)

if err != nil {

log.Fatal(err)

}

ProcessarDados(arquivo, &quot;dados.csv&quot;)

}</code></pre>

<h3>Boas Práticas</h3>

<p><strong>1. Defina interfaces pequenas e focadas.</strong> Não crie interfaces gigantescas com muitos métodos. A interface <code>io.Writer</code> tem apenas um método — <code>Write()</code> — e é uma das mais úteis da stdlib.</p>

<p><strong>2. Aceite interfaces, retorne tipos concretos.</strong> Quando uma função recebe um parâmetro, peça uma interface para máxima flexibilidade. Quando retorna, retorne um tipo concreto para clareza.</p>

<p><strong>3. Comumente, você não precisa de muitas interfaces.</strong> Ao contrário de linguagens como Java, Go incentiva simplicidade. Crie interfaces quando necessário para desacoplamento e testabilidade, não por princípio.</p>

<p><strong>4. Use composição de interfaces.</strong> Combine interfaces menores para criar contratos maiores, em vez de criar uma interface monolítica.</p>

<pre><code class="language-go">type Reader interface {

Read(p []byte) (n int, err error)

}

type Writer interface {

Write(p []byte) (n int, err error)

}

type ReadWriter interface {

Reader

Writer

}</code></pre>

<h2>Referências</h2>

<ul>

<li><a href="https://golang.org/doc/effective_go#interfaces" target="_blank" rel="noopener noreferrer">The Go Programming Language - Interfaces</a></li>

<li><a href="https://gobyexample.com/interfaces" target="_blank" rel="noopener noreferrer">Go by Example - Interfaces</a></li>

<li><a href="https://golang.org/doc/effective_go" target="_blank" rel="noopener noreferrer">Effective Go - Interfaces and other types</a></li>

<li><a href="https://research.swtch.com/interfaces" target="_blank" rel="noopener noreferrer">The Go Blog - Go Data Structures: Interfaces</a></li>

<li><a href="https://www.oreilly.com/library/view/learning-go-2nd/9781492077206/" target="_blank" rel="noopener noreferrer">Jon Bodner - Learning Go, 2nd Edition - Chapter on Interfaces</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

O que Todo Dev Deve Saber sobre Containerizando Aplicações Go: Dockerfile Multi-stage e Distroless
O que Todo Dev Deve Saber sobre Containerizando Aplicações Go: Dockerfile Multi-stage e Distroless

Entendendo o Problema: Por Que Multi-stage e Distroless? Quando você conteine...

O que Todo Dev Deve Saber sobre Embedding em Go: Composição de Structs e Interfaces
O que Todo Dev Deve Saber sobre Embedding em Go: Composição de Structs e Interfaces

Embedding em Go: O que é e Por Que Importa Embedding é um mecanismo poderoso...

O que Todo Dev Deve Saber sobre Variáveis, Constantes, Tipos Primitivos e Inferência de Tipos em Go
O que Todo Dev Deve Saber sobre Variáveis, Constantes, Tipos Primitivos e Inferência de Tipos em Go

Variáveis em Go: Declaração, Atribuição e Escopo Uma variável é um local na m...