Go: Composição vs Herança
Vinicius Feitosa Pacheco
Posted on December 29, 2019
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
}
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"
}
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
}
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)
}
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())
}
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)
}
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.
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
February 17, 2023