Go: Composição vs Herança

viniciuspach

Vinicius Feitosa Pacheco

Posted on December 29, 2019

Go: Composição vs Herança

Primeiramente uma coisa muito importante para dizer é que Go não tem herança. Num primeiro momento para pessoas que estão acostumadas com OOP, o fato de uma linguagem não ter herança parece um grande absurdo. Isso é variável de pessoa para pessoa, no entanto eu prefiro seguir a linha de Joshua Block que diz: “Prefira composição em vez de herança”. Isso porque para Block a herança é uma forma de quebrar o encapsulamento. Martin Fowler trata do mesmo assunto nesse
artigo.

Outro ponto para entender, é que herança NÃO é polimorfismo. O polimorfismo pode ser adquirido de outras formas. Porém é inegável que herança gera economia de código. Mas então, como ter economia de código e polimorfismo? A resposta é muito simples: Interfaces e composição. Go oferece os dois :)

Composição

Primeiramente eu vou falar de composição. No código abaixo eu crio dois tipos: "pai" e "filho"

package main

type pai struct {
    nome string
    idade int
}

type filho struct {
    pai //pai compondo o tipo filho
    email string
}
Enter fullscreen mode Exit fullscreen mode

Observe que o tipo "pai" está compondo o tipo "filho". Na prática isso significa dizer que os campos e métodos que o tipo "pai" possuir também vão estar no tipo filho. Com isso, mesmo sem as propriedades nome e idade escritas diretamente no tipo "filho", pela composição eu consigo acessar as mesmas. Já o tipo "pai" não tem acesso a propriedade email, pois só o "filho" a possui, como no exemplo abaixo:

func main() {
    pai := new(pai)
    pai.nome="João"
    pai.idade=50

    filho := new(filho)
    filho.nome = "Carlos"
    filho.idade = 20
    filho.email = "carlos@teste.com"
}
Enter fullscreen mode Exit fullscreen mode

Interface

O fato da composição existir não significa em nenhum momento que "pai" e "filho" são do mesmo tipo. Para que isso ocorra, temos que fazer através de uma interface

type familia interface {
    dados() string
}
Enter fullscreen mode Exit fullscreen mode

Em Go, para que um tipo assine uma interface basta que o tipo utilize todos os métodos de uma determinada interface. Sendo assim, se quisermos que "pai" e "filho" sejam uma "familia" temos que fazer com que os dois tipos possuam o método "dados".

type pai struct {
    nome string
    idade int
}

func (p pai) dados() string {
    return fmt.Sprintf("Nome: %s, Idade: %d", p.nome, p.idade)
}

type filho struct {
    pai
    email string
}

func (f filho) dados() string {
    return fmt.Sprintf("Nome: %s, Idade: %d, Email: %s", f.nome, f.idade, f.email)
}
Enter fullscreen mode Exit fullscreen mode

Agora tanto "pai" como "filho" além dos seus próprios tipos, são do tipo família. Para mostrar o polimorfismo vou escrever uma função que só recebe o tipo família para mostrar o conteúdo do método "dados".

func mostraDados(membro familia) {
    fmt.Println(membro.dados())
}
Enter fullscreen mode Exit fullscreen mode

Com minhas estruturas, interface, composições e função declaradas, vou chama a função "mostraDados" na minha função "main" para ter o resultado final. Vamos ver como ficou o código completo do exemplo:

package main

import(
    "fmt"
)

type familia interface {
    dados() string
}

type pai struct {
    nome string
    idade int
}

func (p pai) dados() string {
    return fmt.Sprintf("Nome: %s, Idade: %d", p.nome, p.idade)
}

type filho struct {
    pai
    email string
}

func (f filho) dados() string {
    return fmt.Sprintf("Nome: %s, Idade: %d, Email: %s", f.nome, f.idade, f.email)
}

func mostraDados(membro familia) {
    fmt.Println(membro.dados())
}

func main() {
    pai := new(pai)
    pai.nome="João"
    pai.idade=50

    filho := new(filho)
    filho.nome = "Carlos"
    filho.idade = 20
    filho.email = "carlos@teste.com"

    mostraDados(pai)
    mostraDados(filho)
}
Enter fullscreen mode Exit fullscreen mode

Com o código acima nós possuímos a seguinte saída após executar um "go run":

Nome: João, Idade: 50
Nome: Carlos, Idade: 20, Email: carlos@teste.com

Conclusão

Mesmo sem herança, o Go em seu design mostra que é possível trabalhar com reaproveitamento de código e polimorfismo utilizando composição e interfaces. É óbvio que para quem está muito acostumado com heranças é uma quebra de paradigma, mas composição e interfaces além de poderosos são uma ótima prática.

💖 💪 🙅 🚩
viniciuspach
Vinicius Feitosa Pacheco

Posted on December 29, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related