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!
Show de bola … parabéns pelo conteúdo!!