Interfaces
Esta aula aborda o conceito de interfaces em Go, incluindo definição, implementação implícita, interface vazia (any) e polimorfismo. Você aprenderá como as interfaces permitem escrever código flexível e reutilizável, com exemplos práticos e exercícios para fixação.
Interfaces são um dos pilares da programação em Go, permitindo definir contratos comportamentais sem acoplamento rígido. Diferente de linguagens como Java ou C#, Go adota uma abordagem única: a implementação de interfaces é implícita, ou seja, um tipo satisfaz uma interface automaticamente se ele implementar todos os métodos exigidos. Isso torna o código mais flexível e modular.
Nesta aula, vamos explorar desde a definição de interfaces até o uso da interface vazia (any) e como o polimorfismo é alcançado em Go. Ao final, você será capaz de criar sistemas que dependem de comportamentos, não de tipos concretos.
Definição
Uma interface em Go é um tipo abstrato que define um conjunto de assinaturas de métodos. Qualquer tipo que implemente todos esses métodos é considerado compatível com a interface. A sintaxe é simples: usamos a palavra-chave type seguida do nome da interface e a palavra interface, com os métodos dentro de chaves.
Por exemplo, podemos definir uma interface Animal que exige um método Falar() que retorna uma string:
type Animal interface {
Falar() string
}Qualquer tipo que tenha um método Falar() string automaticamente satisfaz a interface Animal. Não é necessário declarar explicitamente que o tipo implementa a interface; isso é verificado em tempo de compilação.
Interfaces podem conter zero ou mais métodos. Quanto mais métodos, mais específico é o contrato. É uma boa prática manter interfaces pequenas (princípio da segregação de interfaces).
Implementação implícita
Em Go, não usamos palavras-chave como implements. A implementação é implícita: se um tipo possui todos os métodos exigidos por uma interface, ele automaticamente satisfaz essa interface. Isso permite que tipos criados em pacotes diferentes implementem interfaces sem depender delas.
Vamos criar dois tipos que implementam a interface Animal:
type Cachorro struct{}
func (c Cachorro) Falar() string {
return "Au au"
}
type Gato struct{}
func (g Gato) Falar() string {
return "Miau"
}Agora, qualquer função que aceite um parâmetro do tipo Animal pode receber tanto Cachorro quanto Gato:
func Apresentar(a Animal) {
fmt.Println(a.Falar())
}
func main() {
cachorro := Cachorro{}
gato := Gato{}
Apresentar(cachorro)
Apresentar(gato)
}Isso demonstra o polimorfismo: o mesmo código funciona com diferentes tipos concretos, desde que eles implementem a interface. A implementação implícita permite que bibliotecas de terceiros implementem suas interfaces sem precisar importá-las.
Interface vazia (any)
A interface vazia, representada como interface{} ou, a partir do Go 1.18, como any, é uma interface que não exige nenhum método. Portanto, todos os tipos a satisfazem automaticamente. Ela é útil quando precisamos armazenar valores de tipos arbitrários, como em containers heterogêneos.
Exemplo de uso com any:
func PrintValue(v any) {
fmt.Printf("Valor: %v, Tipo: %T\n", v, v)
}
func main() {
PrintValue(42)
PrintValue("hello")
PrintValue(3.14)
}No entanto, ao usar any, perdemos a segurança de tipos. Para recuperar o valor original, precisamos fazer uma type assertion ou usar type switches. Exemplo de type assertion:
var x any = 10
numero, ok := x.(int)
if ok {
fmt.Println("É um inteiro:", numero)
} else {
fmt.Println("Não é inteiro")
}Type switch é uma construção que permite testar múltiplos tipos:
switch v := x.(type) {
case int:
fmt.Println("Inteiro:", v)
case string:
fmt.Println("String:", v)
default:
fmt.Println("Desconhecido")
}Evite usar any desnecessariamente; prefira interfaces específicas para manter a segurança de tipos.
Polimorfismo
Polimorfismo é a capacidade de um objeto assumir diferentes formas. Em Go, ele é alcançado através de interfaces. Uma variável de tipo interface pode armazenar qualquer valor que implemente essa interface, e o método chamado será o correspondente ao tipo concreto armazenado.
Vamos estender o exemplo dos animais para incluir um método Andar() e demonstrar polimorfismo com uma slice de interfaces:
type Animal interface {
Falar() string
Andar() string
}
type Cachorro struct{}
func (c Cachorro) Falar() string { return "Au au" }
func (c Cachorro) Andar() string { return "Cachorro andando" }
type Gato struct{}
func (g Gato) Falar() string { return "Miau" }
func (g Gato) Andar() string { return "Gato andando" }
type Passaro struct{}
func (p Passaro) Falar() string { return "Piu piu" }
func (p Passaro) Andar() string { return "Pássaro voando" }
func main() {
animais := []Animal{Cachorro{}, Gato{}, Passaro{}}
for _, a := range animais {
fmt.Println(a.Falar(), "-", a.Andar())
}
}Isso imprime comportamentos diferentes para cada animal, demonstrando polimorfismo. O tipo Animal é a interface que abstrai o comportamento comum.
Outro exemplo comum é a interface error da biblioteca padrão, que permite que qualquer tipo que implemente o método Error() string seja tratado como erro. Isso possibilita criar tipos de erro customizados.
Boas práticas: projete interfaces pequenas e focadas em comportamento. A biblioteca padrão do Go segue esse princípio, como io.Reader e io.Writer.
Referências
- Effective Go: Interfaces
- Go Specification: Interface types
- Go Blog: Type switches
- Package builtin: any
- Go FAQ: Generics and interfaces
Exercícios
Crie uma interface
Formacom um métodoArea() float64. Implemente-a para os tiposRetangulo(com campos Largura e Altura) eCirculo(com campo Raio). Escreva uma função que receba umaFormae imprima sua área.✓ Resposta:package main import "fmt" type Forma interface { Area() float64 } type Retangulo struct { Largura, Altura float64 } func (r Retangulo) Area() float64 { return r.Largura * r.Altura } type Circulo struct { Raio float64 } func (c Circulo) Area() float64 { return 3.14159 * c.Raio * c.Raio } func ImprimirArea(f Forma) { fmt.Printf("Área: %v\n", f.Area()) } func main() { r := Retangulo{Largura: 3, Altura: 4} c := Circulo{Raio: 5} ImprimirArea(r) ImprimirArea(c) }Explique por que a seguinte afirmação é verdadeira: "Em Go, interfaces são implementadas implicitamente". Dê um exemplo.
✓ Resposta:Em Go, não é necessário declarar explicitamente que um tipo implementa uma interface. Basta que o tipo possua todos os métodos exigidos pela interface. O compilador verifica isso automaticamente. Exemplo: a interfaceStringerda biblioteca padrão exige o métodoString() string. Qualquer tipo que tenha esse método implementaStringersem precisar de uma declaraçãoimplements.O que é a interface vazia (
any) e quando devemos utilizá-la? Cite um exemplo de uso adequado e outro inadequado.✓ Resposta:A interface vazia (any) não exige nenhum método, portanto todos os tipos a implementam. Deve ser usada quando precisamos armazenar valores de tipos arbitrários, como em funções de logging genéricas ou containers heterogêneos. Exemplo adequado:fmt.Printlnusaany. Exemplo inadequado: usaranypara parâmetros de funções que poderiam ser tipados, pois perde segurança de tipos.Escreva um programa que utilize uma interface
Imprimivelcom métodoImprimir() string. Crie dois tipos concretos que implementem essa interface e uma função que receba um slice deImprimivele imprima todos os elementos.✓ Resposta:package main import "fmt" type Imprimivel interface { Imprimir() string } type Documento struct { Conteudo string } func (d Documento) Imprimir() string { return d.Conteudo } type Foto struct { Nome string } func (f Foto) Imprimir() string { return "[Foto: " + f.Nome + "]" } func ImprimirTudo(itens []Imprimivel) { for _, item := range itens { fmt.Println(item.Imprimir()) } } func main() { itens := []Imprimivel{ Documento{Conteudo: "Texto do documento"}, Foto{Nome: "praia.jpg"}, } ImprimirTudo(itens) }Como o polimorfismo é alcançado em Go? Compare com linguagens como Java ou C++.
✓ Resposta:Em Go, o polimorfismo é alcançado através de interfaces. Uma variável de tipo interface pode armazenar qualquer valor que implemente essa interface, e a chamada de método é resolvida dinamicamente (dispatch virtual). Diferente de Java (herança de classe) ou C++ (herança e funções virtuais), Go não possui herança de classes; o polimorfismo é baseado em composição e interfaces implícitas. Isso torna o código mais flexível e menos acoplado.