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.
Top comments (0)