Programação Orientada a Objetos: Herança
Fabiano Santos Florentino
Posted on June 27, 2024
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
Posted on June 27, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.