Herança
Herança é um dos pilares da programação orientada a objetos, e é uma das formas de reutilização de código. A herança é um mecanismo que permite que uma classe herde atributos e métodos de outra classe, chamada de superclasse ou classe base. A classe que herda os atributos e métodos é chamada de subclasse ou classe derivada.
Principais Conceitos
- Superclasse (Classe Base): A classe cujos atributos e métodos são herdados por outras classes. É a classe “pai”.
- Subclasse (Classe Derivada): A classe que herda atributos e métodos da superclasse. É a classe “filha”.
- Herança Simples: Quando uma subclasse herda de uma única superclasse.
- Herança Múltipla: Quando uma subclasse herda de mais de uma superclasse. Nem todas as linguagens de programação suportam herança múltipla devido à sua complexidade.
- Sobrescrita de Método: A subclasse pode fornecer uma implementação específica de um método que já existe na superclasse.
Herança & Composição não são a mesma coisa
Composição e herança são duas formas de reutilização de código em programação orientada a objetos. A herança é uma forma de reutilização de código que permite que uma classe herde atributos e métodos de outra classe. A composição é uma forma de reutilização de código que permite que uma classe contenha objetos de outras classes. A composição é geralmente preferida à herança, pois é mais flexível e menos propensa a problemas de design.
Como funciona em Go
Go não possui herança
como em linguagens orientadas a objetos clássicas. Ao invés de herança, Go utiliza composição e interfaces para alcançar o mesmo comportamento. Geralmente, a composição é feita através de structs (estruturas) e interfaces.
package main
import "fmt"
// Veiculo é uma interface que define um método dados que retorna uma string
type Veiculo interface {
dados() string
}
// Carro é uma struct que representa um carro
type Carro struct {
marca string
modelo string
}
// dados é um método que retorna uma string com os dados do carro
func (c Carro) dados() string {
return fmt.Sprintf("Marca: %s, Modelo: %s", c.marca, c.modelo)
}
// Hatch é uma struct que representa um carro do tipo hatch
type Hatch struct {
Carro
portas int
}
// dados é um método que retorna uma string com os dados do carro hatch
func (h Hatch) dados() string {
return fmt.Sprintf("Marca: %s, Modelo: %s, Portas: %d", h.marca, h.modelo, h.portas)
}
// Sedan é uma struct que representa um carro do tipo sedan
type Sedan struct {
Carro
portaMalas int
}
// dados é um método que retorna uma string com os dados do carro sedan
func (s Sedan) dados() string {
return fmt.Sprintf("Marca: %s, Modelo: %s, Porta Malas: %d", s.marca, s.modelo, s.portaMalas)
}
type Conversivel struct {
Carro
capota bool
}
func (c Conversivel) dados() string {
return fmt.Sprintf("%s, Capota: %t", c.Carro.dados(), c.capota)
}
// imprimirDados é uma função que recebe um veículo e imprime os dados do veículo
func imprimirDados(v Veiculo) {
fmt.Println(v.dados())
}
func main() {
// Acessando atributos
hatch := Hatch{Carro{"Chevrolet", "Onix"}, 4}
sedan := Sedan{Carro{"Honda", "Civic"}, 500}
// Acessando métodos dados da struct Carro de forma explícita
conversivel := Conversivel{Carro{"Fiat", "Spyder"}, true}
imprimirDados(hatch)
imprimirDados(sedan)
imprimirDados(conversivel)
}
heranca main 29m ➜ go run main.go
Marca: Chevrolet, Modelo: Onix, Portas: 4
Marca: Honda, Modelo: Civic, Porta Malas: 500
Marca: Fiat, Modelo: Spyder, Capota: true
Neste exemplo, temos uma interface Veiculo
que define um método dados
que retorna uma string. Temos também uma struct Carro
que representa um carro e um método dados
que retorna uma string com os dados do carro. As structs Hatch
e Sedan
representam carros do tipo hatch e sedan, respectivamente. Ambas as structs Hatch
e Sedan
incorporam a struct Carro
através da composição. Cada uma das structs Hatch
e Sedan
tem um método dados
que retorna uma string com os dados do carro do tipo hatch ou sedan. A função imprimirDados
recebe um veículo e imprime os dados do veículo.
Com a composição você pode acessar tantos os atributos quanto os métodos da struct incorporada. No entanto, se houver um método com o mesmo nome ele será sobrescrito.Você pode acessar o método da struct incorporada de forma explícita c.Carro.dados()
.
func (h Hatch) dados() string {
return fmt.Sprintf("%s, Portas: %d", h.Carro.dados(), h.portas)
}
Conclusão
A herança é um mecanismo importante da programação orientada a objetos que permite a reutilização de código. No entanto, a herança pode levar a problemas de design, como acoplamento excessivo e hierarquias de classes profundas. Em Go, a linguagem não possui herança como em linguagens orientadas a objetos clássicas. Em vez disso, Go utiliza composição e interfaces para alcançar o mesmo comportamento. A composição é geralmente preferida à herança, pois é mais flexível e menos propensa a problemas de design.
Projeto
Referências
Wikipédia (Herança)
Wikipédia (Composição, herança e delegação)
Go: Composição vs Herança (Vinicius Pacheco)
Effective Go
The Go Programming Language Specification
Go by Example