Interfaces: contratos implícitos
1. O que são interfaces em Go?
Em Go, uma interface define um contrato comportamental — um conjunto de métodos que um tipo deve implementar para satisfazer a interface. Diferente de linguagens como Java ou C#, Go não exige que você declare explicitamente que um tipo implementa uma interface. A implementação é implícita: se um tipo possui todos os métodos declarados na interface, ele automaticamente a satisfaz.
A sintaxe básica é simples:
type Animal interface {
EmitirSom() string
}
Qualquer tipo que tenha um método EmitirSom() string automaticamente implementa Animal.
2. Implementação implícita: o coração das interfaces
A implementação implícita é o que torna as interfaces em Go tão poderosas. Vejamos um exemplo prático:
package main
import "fmt"
type Animal interface {
EmitirSom() string
}
type Cachorro struct {
Nome string
}
func (c Cachorro) EmitirSom() string {
return "Au au!"
}
type Gato struct {
Nome string
}
func (g Gato) EmitirSom() string {
return "Miau!"
}
func Apresentar(a Animal) {
fmt.Println(a.EmitirSom())
}
func main() {
cachorro := Cachorro{Nome: "Rex"}
gato := Gato{Nome: "Mimi"}
Apresentar(cachorro) // Au au!
Apresentar(gato) // Miau!
}
Note que nem Cachorro nem Gato declaram explicitamente que implementam Animal. A satisfação é automática. Isso traz desacoplamento, flexibilidade e código mais limpo.
3. Composição de interfaces
Go permite compor interfaces através de embedding. Uma interface pode incorporar outras interfaces, formando contratos mais complexos:
package main
import (
"fmt"
"io"
"strings"
)
type LeitorEscritor interface {
io.Reader
io.Writer
}
func ProcessarDados(rw LeitorEscritor, dados []byte) (int, error) {
n, err := rw.Write(dados)
if err != nil {
return 0, err
}
buf := make([]byte, n)
_, err = rw.Read(buf)
if err != nil {
return 0, err
}
fmt.Printf("Dados lidos: %s\n", buf)
return n, nil
}
func main() {
var buf strings.Builder
ProcessarDados(&buf, []byte("Olá, Go!"))
}
A stdlib é rica em exemplos: io.ReadWriter, io.ReadCloser, io.WriteCloser são todas interfaces compostas.
4. Satisfação de interface e type assertions
Em tempo de execução, podemos verificar se um valor implementa uma interface específica usando type assertion:
package main
import "fmt"
type Stringer interface {
String() string
}
func Descrever(v interface{}) {
if s, ok := v.(Stringer); ok {
fmt.Println("Implementa Stringer:", s.String())
} else {
fmt.Println("Não implementa Stringer")
}
}
type Pessoa struct {
Nome string
}
func (p Pessoa) String() string {
return p.Nome
}
func main() {
Descrever(Pessoa{Nome: "João"}) // Implementa Stringer: João
Descrever(42) // Não implementa Stringer
}
O padrão valor.(TipoInterface) retorna dois valores: o valor convertido e um booleano indicando sucesso. Isso evita pânicos.
5. Interfaces nil vs valores concretos nil
Uma das armadilhas mais comuns em Go é a diferença entre uma interface com valor nil e uma interface que contém um ponteiro nil:
package main
import "fmt"
type Animal interface {
EmitirSom() string
}
type Cachorro struct{}
func (c *Cachorro) EmitirSom() string {
return "Au au!"
}
func VerificarAnimal(a Animal) {
if a == nil {
fmt.Println("Animal é nil")
} else {
fmt.Printf("Animal não é nil, tipo: %T, valor: %v\n", a, a)
}
}
func main() {
var a1 Animal // interface nil
var c *Cachorro = nil // ponteiro nil
VerificarAnimal(a1) // Animal é nil
var a2 Animal = c // interface com valor concreto nil
VerificarAnimal(a2) // Animal não é nil, tipo: *main.Cachorro, valor: <nil>
}
Para evitar bugs, use reflexão ou type switch para verificar valores concretos nil:
import "reflect"
func IsNil(a Animal) bool {
return a == nil || reflect.ValueOf(a).IsNil()
}
6. Type switches com interfaces
O type switch permite tratar diferentes tipos concretos que implementam a mesma interface:
package main
import (
"fmt"
"math"
)
type Forma interface {
Area() float64
}
type Quadrado struct {
Lado float64
}
func (q Quadrado) Area() float64 {
return q.Lado * q.Lado
}
type Circulo struct {
Raio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
type Triangulo struct {
Base float64
Altura float64
}
func (t Triangulo) Area() float64 {
return (t.Base * t.Altura) / 2
}
func DescreverForma(f Forma) {
switch v := f.(type) {
case Quadrado:
fmt.Printf("Quadrado de lado %.2f, área: %.2f\n", v.Lado, v.Area())
case Circulo:
fmt.Printf("Círculo de raio %.2f, área: %.2f\n", v.Raio, v.Area())
case Triangulo:
fmt.Printf("Triângulo de base %.2f e altura %.2f, área: %.2f\n", v.Base, v.Altura, v.Area())
default:
fmt.Printf("Forma desconhecida, área: %.2f\n", f.Area())
}
}
func main() {
formas := []Forma{
Quadrado{Lado: 4},
Circulo{Raio: 3},
Triangulo{Base: 5, Altura: 6},
}
for _, f := range formas {
DescreverForma(f)
}
}
7. Boas práticas e padrões comuns
- Interfaces pequenas e focadas: Siga o Princípio da Segregação de Interfaces (ISP). Uma interface com 1-3 métodos é ideal.
- Nomeação com sufixo -er: Para interfaces de ação, use o sufixo
-er:Stringer,Reader,Writer,Closer. - Prefira interfaces em parâmetros, tipos concretos em retornos: Aceite interfaces, retorne structs.
- Não crie interfaces desnecessárias: Espere até que um padrão de uso justifique a criação de uma interface.
Exemplo de boa prática:
// Aceita interface, retorna struct
func Salvar(w io.Writer, dados []byte) error {
_, err := w.Write(dados)
return err
}
Referências
- Effective Go: Interfaces — Documentação oficial com melhores práticas sobre interfaces em Go
- Go by Example: Interfaces — Tutorial prático com exemplos interativos de implementação de interfaces
- The Go Blog: Interfaces in Go — Artigo técnico detalhado sobre o design e funcionamento das interfaces
- Understanding Go Interfaces — Tutorial da DigitalOcean com exemplos avançados e type assertions
- Go Interfaces: A Practical Guide — Guia prático com padrões de composição e boas práticas
- Interface Segregation in Go — Artigo da Ardan Labs sobre segregação de interfaces e design patterns