Introdução a interfaces em Go

Interfaces te ajudam a escrever código modular e reutilizável, o que é indispensável para o desenvolvimento de software moderno.

Toda variável ou constante em Go tem um tipo. Toda função precisa especificar o tipo de todos seus argumentos. Isso significa que pra uma função atender diferentes tipos, ela precisa ser reescrita diversas vezes? Mesmo se a lógica interna não mudar em nada?
Um dos problemas que as Interfaces em Go podem resolver é que elas tornam mais fácil o reuso de código no seu sistema.

Escrever código modular e reutilizável é indispensável para o desenvolvimento de software moderno. Isso garante código funcional e testado em outros projetos e também evita ter que escrever o mesmo código várias vezes.

Go não é uma linguagem orientada à objetos por definição onde, por exemplo, poderíamos usar uma herança para alcançar objetivos similares. Em Go vamos trabalhar com composição utilizando interfaces.

As interfaces em Go são diferentes de outras linguagens. Em Go, uma interface é um tipo personalizado que é usado para especificar um conjunto de uma ou mais assinaturas de método. A interface é abstrata, portanto não se pode criar uma instância de uma interface. Mas você pode criar uma variável de um tipo da interface e esta variável pode ser atribuída com um valor de tipo concreto que tenha os métodos que a interface requer. Ou em outras palavras, a interface é uma coleção de assinaturas de métodos, assim como é um tipo personalizado. Mas o que é a assinatura de um método. Vejamos o exemplo:

func WriteLog(string) error {}

Essa é assinatura do método WriteLog. Em sua declaração vemos que ele aceita uma string como argumento e retorna um tipo error, porém não tem a implementação em si. Vejamos agora como é a declaração de uma interface:

type Metrica interface {
    Area() float64
}

Aqui criamos a interface chamada Metrica. Essa interface declara um método chamado Area. Esse método não recebe nenhum argumento e retorna um valor do tipo float64.
Em Go, para um tipo qualquer implementar uma interface, basta que ele implemente todos os seus métodos. Diferente de outras linguagens onde, para se implementar uma interface você precisa declarar na criação da classe, por exemplo em Java:

public class MinhaClasse implements MinhaInterface {...}

Vamos a um exemplo. Usaremos a interface já citada e criaremos mais dois tipos:

package main

type Metrica interface { 
    Area() float64
}

type Circulo struct {
    Raio float64
}

type Quadrado struct {
    Largura  float64
    Altura float64
}

Tipo Circulo e tipo Quadrado. Cada um com seus respectivos atributos. Agora vamos criar um método Area para cada tipo:

package main 

import ( 
    "fmt" 
    "math" 
) 

type Metrica interface { 
    Area() float64 
} 

type Circulo struct { 
    Raio float64 
} 

func (c Circulo) Area() float64 { 
    return math.Pi * math.Pow(c.Raio, 2) 
} 

type Quadrado struct { 
    Largura float64 Altura float64 
}

func (q Quadrado) Area() float64 { 
    return q.Largura * q.Altura 
}

Veja agora que cada um dos tipos implementa o método Area, cada um com sua respectiva lógica. Sendo assim os tipos Circulo e Quadrado implementam a interface Metrica. E já podemos dizer que Circulo e Quadrado são, inclusive, do tipo Metrica. Mas o que tudo isso significa? Veja o resto do código:

package main 

import ( 
    "fmt" 
    "math" 
) 

type Metrica interface { 
    Area() float64 
} 

type Circulo struct { 
    Raio float64 
} 

func (c Circulo) Area() float64 { 
    return math.Pi * math.Pow(c.Raio, 2) 
} 

type Quadrado struct { 
    Largura float64 Altura float64 
}

func (q Quadrado) Area() float64 { 
    return q.Largura * q.Altura 
}

func printArea(m Metrica) {
    fmt.Printf("A área do objeto é %f", m.Area())
}

O método printArea aceita como parâmetro um valor do tipo Metrica, nossa interface. Ou seja, qualquer tipo que implemente a interface Metrica pode ser passado como parâmetro para a função printArea. Vejamos a seguir:

package main 

import ( 
    "fmt" 
    "math" 
) 

type Metrica interface { 
    Area() float64 
} 

type Circulo struct { 
    Raio float64 
} 

func (c Circulo) Area() float64 { 
    return math.Pi * math.Pow(c.Raio, 2) 
} 

type Quadrado struct { 
    Largura float64 Altura float64 
}

func (q Quadrado) Area() float64 { 
    return q.Largura * q.Altura 
}

func printArea(m Metrica) {
    fmt.Printf("A área do objeto é %f", m.Area())
}

func main() {
    c := Circulo{Raio: 20}
    q := Quadrado{Largura: 10, Altura: 10}
    
    // Printa a área do quadrado
    printArea(q)

    // Printa a área do círculo
    printArea(c)
}

Veja que o mesmo método printArea aceita ambos os tipos Quadrado e Circulo, pois ambos os tipos também são do tipo Metrica. A função printArea pode receber qualquer tipo que implemente a interface Metrica. Poderíamos definir mais tipos como Triangulo, Retângulo, Hexágono, etc. Contanto que todos satisfaçam a condição da interface Metrica (implementar o método Area), todos esses tipos automaticamente se tornam do tipo Metrica e  podem ser passados como parâmetro para printArea.

Quando uma interface não tem nenhum método ela é conhecida como uma interface vazia. Todos o tipos em Go implementam a interface vazia:
interface{}

Interface em Go é um assunto mais extenso, portanto criarei uma série de posts sobre o assunto.

Grande abraço e até a próxima!

1 comment
Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post Anterior

Trabalhando com Go no VS Code

Próximo Post

Interfaces em Go: Explorando Usos Avançados

Posts Relacionados

Concorrência em Go com Goroutines

Descubra o poder das goroutines em Go para concorrência eficiente. Aprenda canais, select e tratamento de erros, além das diferenças com threads. Torne seus programas mais escaláveis e eficazes.
Leia Mais