GO: Pacotes adaptáveis

Ou o reino das interfaces personalizáveis

Olá, leitores do CodeAspiras! Prometo que depois desse artigo eu vou escrever qualquer outra coisa que não seja Golang, beleza? É que, no momento, todo o meu foco está nessa linguagem de programação e, por isso, está mais fácil escrever sobre ela.

Dessa vez, trago mais um assunto bem relevante pra quem está começando a desenvolver nessa linguagem, principalmente se você ainda não criou o hábito de escrever interfaces. Vamos começar?

Interfaces

Antes de mais nada, é preciso entender que interfaces são contratos. Elas não tem nenhum código atrelado a elas diretamente, apenas dizem: "o que quer que esteja implementando isso aqui, está de acordo com as assinaturas desta interface".

Diferente de outras linguagens, Golang não tem o conceito de classe da Orientação a Objeto. Isso por si só já foi motivo de ódio de muitos, mas calma. Na realidade, isso expandiu as possibilidades do que você pode fazer. Ao invés de ser obrigado a anunciar quais interfaces suas structs implementam, basta que as structs implementem as funções da assinatura das interfaces que, automaticamente, já será interpretada como implementação.

Meme: o quê?

QUÊ?!

Calma, calma, calma... Vamos comparar para entender melhor.

Interfaces em PHP

Se você cria, no PHP, uma interface cuja assinatura declara que suas implementações possuem uma função chamada Ping() que retorna uma string, toda classe que implementar essa interface deverá ter essa função:

interface PingableInterface {
    public function Ping(): string;
}

class Pingable implements PingableInterface {
    public function Ping(): string {
        return "pong";
    }
}

Porém, se você fizer uma classe com a mesma função sem declarar na hierarquia que a mesma está implementado a interface, o interpretador entenderá que não implementa a assinatura:

interface PingableInterface {
    public function Ping(): string;
}

class Pingable implements PingableInterface {
    public function Ping(): string {
        return "pong";
    }
}

class Pingable2 {
    public function Ping(): string {
        return "pong";
    }
}

var_dump(is_a(new Pingable(), 'PingableInterface')); // true
var_dump(is_a(new Pingable2(), 'PingableInterface')); // false - é exatamente o mesmo código do Pingable, só que sem a declaração "implements PingableInterface"

Experimente o código: https://onlinephp.io/c/38b0a

Interfaces em Golang

Aqui, isso não acontece, dado que nem existe opção para você declarar que uma "struct implementa uma interface". As implementações são dinâmicas, então basta que sua struct esteja de acordo com a assinatura das interfaces para que o interpretador aceite-a como implementação.

package main

import "fmt"

type PingableInterface interface {
    Ping() string
}

type Pingable struct{} // não tem nada como "implements PingableInterface"

func (p Pingable) Ping() string {
    return "pong"
}

func main() {
    var pingable interface{} = Pingable{}
    if _, is := pingable.(PingableInterface); is { // equivalente ao is_a() do PHP
        fmt.Println("implements PingableInterface")
    } else {
        fmt.Println("does not implement PingableInterface")
    }
}

Experimente o código: https://go.dev/play/p/GKHEC6bd-CT

E com isso, você consegue criar structs que implementam interfaces de terceiros e vice-versa. Mesmo se a interface for algo interno de alguma biblioteca importada, basta que os pacotes se comuniquem por interfaces ao invés de implementações. É isso que é um "pacote adaptável": é um pacote onde todas as chamadas públicas são tratadas por interfaces. Eu usei o termo "adaptável" aqui derivando do Design Pattern "Adapter". Recomendo a leitura!

Escrevendo um pacote regido por interfaces

Uma forma simples de atingir esse objetivo é seguir as seguintes regrinhas:

  1. Se vai expor alguma coisa, tem de estar em uma interface;

  2. Toda implementação real tem de ser privada, restrita ao pacote.

No Golang, tudo que começa com letra maiúscula é público, ou seja, toda nomenclatura que você escreve é case sensitive (a caixa alta/baixa importa). Então é considerado uma boa prática escrever a interface com letra inicial maiúscula e a sua implementação usando o mesmo nome, mas com letra inicial minúscula.

package exping

type Pingable interface {
    Ping() string
}

type pingable struct {}
func (p *pingable) Ping() string {
    return "pong"
}

Assim, quando você fizer uma função de inicialização, essa função deverá declarar que retorna a interface. Mas, na prática, retorna sua implementação privada:

package exping

func NewPingable() Pingable {
    return &pingable{}
}

E em todo lugar que você precisar dessa implementação, você sempre irá referenciar pela interface, nunca pela struct:

package expong

func HandlePing(p exping.Pingable) {
    // ...
    p.Ping()
}

O que eu ganho com isso, Kiko?

Bem... Quando você for escrever testes unitários, você poderá mockar a interface (usando mockery ou qualquer outra coisa, você pode até escrever a implementação de mock na mão, se quiser). Se o que estiver desenvolvendo for uma biblioteca, você estará permitindo que seus consumidores possam injetar implementações próprias ao invés de usar o que você fez... Por ser interface, você também pode passar dados nulos e criar um fluxo mais flexível, coisa que não seria possível ao passar uma struct sem referência. Isso também permite que você faça cast para outras interfaces.

Enfim, tem muitos usos para isso. No geral, eu gosto de pensar que isso torna o código um pouco mais seguro e dá mais possibilidades do que podemos fazer posteriormente. E é por isso que eu acredito que esse seja um conhecimento relevante para novatos da linguagem.

Ah, Kiko, então só vale a pena fazer interfaces em Golang?

De jeito nenhum! Interfaces são contratos. Ao anunciar qual estrutura sua implementação irá seguir, fica mais fácil para todos de entender como o código irá se comportar. Então, sempre que uma linguagem der suporte para interfaces, dê um jeito de aprender a usar!


E por hoje é só! Curtiu? Comenta e compartilha! Lembrando que ainda temos muitas vagas para colaboradores do blog. Sinta-se a vontade para entrar em contato e começar a mandar suas publicações. Sua conta irá aparecer como autor(a) e você aparecerá na página de membros do blog. Isso é um incentivo para que cada um possa fazer sua parte de girar a roda da comunidade, ok?

E eu vou indo nessa. Abraço!

Inté!