Programação Orientada a Objetos: Herança

fabianoflorentino

Fabiano Santos Florentino

Posted on June 27, 2024

Programação Orientada a Objetos: Herança

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)
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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

Github

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

💖 💪 🙅 🚩
fabianoflorentino
Fabiano Santos Florentino

Posted on June 27, 2024

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

Sign up to receive the latest update from our blog.

Related