Desenvolvimento

3 set, 2018

Interface como um contrato

Publicidade

Falamos desse mesmo tema na nossa lista de exemplos do grupo de estudos sobre interfaces, mas vamos isso foi lá no longínquo ano de 2016, então vamos tentar fazer um novo exemplo para mostrar como usar uma interface como um contrato que permite você passar tipos diferentes para uma função ou aceitar tipos diferentes em uma interface desde que ele aceite e implemente o contrato, que em outras palavras significa implementar as funções descritas na interface.

No exemplo abaixo temos um pequeno programa que implementa a struct melancia, e daí usa essa struct como parâmetro e também para iniciar uma variável. Funciona, mas usar tipos concretos dessa forma engessa o programa. Não é possível interferir nas funções que serão executadas uma vez que não é possível alterar a função que vai ser chamada.

// usando tipos concretos
package main

import (
	"fmt"
)

type melancia struct {
}

func (m melancia) nome() string {
	return "melancia"
}

func vitaminaDe(m melancia) {
	fmt.Println("vitamina de", m.nome())
}

func main() {
	var x melancia
	x = melancia{}
	nome := x.nome()
	fmt.Println(nome)

	vitaminaDe(x)
}

Agora vamos fazer uma pequena alteração em nosso programa, e em vez de usar a struct diretamente vamos usar uma interface tanto para declarar a variável como para passar o parâmetro para a função.

Note a interface fruta no exemplo abaixo e note que ela implementa a função nome(). Qualquer struct que implemente essa função pode ser usada como parâmetro para a função, tornando o programa bem mais flexível, permitindo receber diferentes tipos de dados.

// usando interface
package main

import (
	"fmt"
)

type fruta interface {
	nome() string
}

type melancia struct {
}

func (b melancia) nome() string {
	return "melancia"
}

func vitaminaDe(f fruta) {
	fmt.Println("vitamina de", f.nome())
}

func main() {
	var x fruta
	x = melancia{}
	nome := x.nome()
	fmt.Println(nome)

	vitaminaDe(x)
}

Mais um exemplo usando as duas structs. Como as duas structs satisfazem nosso contrato, não podemos passar qualquer uma. Isso é ótimo, por exemplo quando se quer fazer mock para testes de um pacote.

Se você usar uma interface na hora de receber os parâmetros na hora do teste, é só substituir a instancia da struct original por uma mais adequada para o teste.

package main

import (
	"fmt"
)

type fruta interface {
	nome() string
}

type melancia struct {
}

func (m melancia) nome() string {
	return "melancia"
}

type banana struct {
}

func (b banana) nome() string {
	return "banana"
}

func vitaminaDe(f fruta) {
	fmt.Println("vitamina de", f.nome())
}

func main() {
	var m fruta
	m = melancia{}
	nome := m.nome()
	fmt.Println(nome)

	vitaminaDe(m)

	b := banana{}
	nome = b.nome()
	fmt.Println(nome)

	vitaminaDe(b)
}

E por fim um pequeno truque: saiba que se sua struct implementar uma função String() quando você usar ela com o pacote fmt em funções como Print, por exemplo, o sistema vai automaticamente usar a sua função para converter a instancia da sua struct para string. Muito bom para compor logs melhores.

Links úteis

Código fonte dos exemplos de hoje: