DEV Community

Emanoel Carvalho
Emanoel Carvalho

Posted on

Herança e Classes Abstratas em Programação Orientada a Objetos

O que é Herança?

Herança é um dos pilares fundamentais da Programação Orientada a Objetos (POO) que permite que uma classe (chamada de classe filha ou subclasse) herde atributos e métodos de outra classe (chamada de classe pai ou superclasse). Esse conceito promove a reutilização de código e a criação de hierarquias de classes.

Por que Usar Herança?

A herança é utilizada para:

  • Reutilização de Código: Permite que subclasses reutilizem métodos e atributos da superclasse, reduzindo a duplicação de código.
  • Organização e Estrutura: Facilita a organização de classes em uma hierarquia lógica, representando relações "é um".
  • Extensibilidade: Facilita a adição de novas funcionalidades ao sistema sem alterar o código existente.

Como Funciona a Herança?

Em Java, a herança é implementada usando a palavra-chave extends. A subclasse herda todos os métodos e atributos da superclasse, podendo também adicionar seus próprios métodos e atributos ou sobrescrever os existentes.

Exemplo de Herança

Vamos criar um exemplo simples que demonstra o conceito de herança utilizando uma classe base chamada Animal e duas subclasses chamadas Cachorro e Gato.

// Superclasse
public class Animal {
    // Atributo comum a todos os animais
    private String nome;

    // Construtor
    public Animal(String nome) {
        this.nome = nome;
    }

    // Método comum a todos os animais
    public void fazerSom() {
        System.out.println("O animal faz um som.");
    }

    // Getter para o nome
    public String getNome() {
        return nome;
    }
}
Enter fullscreen mode Exit fullscreen mode
// Subclasse Cachorro
public class Cachorro extends Animal {
    // Construtor da subclasse
    public Cachorro(String nome) {
        super(nome); // Chama o construtor da superclasse
    }

    // Sobrescrevendo o método fazerSom
    @Override
    public void fazerSom() {
        System.out.println(getNome() + " faz au au.");
    }
}
Enter fullscreen mode Exit fullscreen mode
// Subclasse Gato
public class Gato extends Animal {
    // Construtor da subclasse
    public Gato(String nome) {
        super(nome); // Chama o construtor da superclasse
    }

    // Sobrescrevendo o método fazerSom
    @Override
    public void fazerSom() {
        System.out.println(getNome() + " faz miau.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Usando as Classes

public class Main {
    public static void main(String[] args) {
        // Criando instâncias de Cachorro e Gato
        Cachorro cachorro = new Cachorro("Rex");
        Gato gato = new Gato("Miau");

        // Chamando o método fazerSom
        cachorro.fazerSom(); // Saída: Rex faz au au.
        gato.fazerSom();     // Saída: Miau faz miau.
    }
}
Enter fullscreen mode Exit fullscreen mode

Saída Esperada:

Rex faz au au.
Miau faz miau.
Enter fullscreen mode Exit fullscreen mode

O que são Classes Abstratas?

Classes abstratas são um tipo especial de classe que não pode ser instanciada diretamente. Elas servem como uma base para outras classes e podem conter métodos abstratos (métodos sem implementação) e métodos concretos (com implementação). O principal propósito das classes abstratas é fornecer uma estrutura comum para suas subclasses, que devem implementar os métodos abstratos.

Como Funciona uma Classe Abstrata?

Em Java, para declarar uma classe abstrata, usamos a palavra-chave abstract. Métodos declarados como abstract dentro de uma classe abstrata não têm corpo e devem ser implementados nas subclasses.

Exemplo de Classe Abstrata

Vamos modificar o exemplo anterior para incluir uma classe abstrata chamada AnimalAbstrato:

// Classe Abstrata
public abstract class AnimalAbstrato {
    private String nome;

    public AnimalAbstrato(String nome) {
        this.nome = nome;
    }

    // Método abstrato
    public abstract void fazerSom();

    // Getter para o nome
    public String getNome() {
        return nome;
    }
}
Enter fullscreen mode Exit fullscreen mode
// Subclasse Cachorro que herda de AnimalAbstrato
public class Cachorro extends AnimalAbstrato {
    public Cachorro(String nome) {
        super(nome); // Chama o construtor da superclasse
    }

    // Implementando o método fazerSom
    @Override
    public void fazerSom() {
        System.out.println(getNome() + " faz au au.");
    }
}
Enter fullscreen mode Exit fullscreen mode
// Subclasse Gato que herda de AnimalAbstrato
public class Gato extends AnimalAbstrato {
    public Gato(String nome) {
        super(nome); // Chama o construtor da superclasse
    }

    // Implementando o método fazerSom
    @Override
    public void fazerSom() {
        System.out.println(getNome() + " faz miau.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Usando a Classe Abstrata

public class Main {
    public static void main(String[] args) {
        // Criando instâncias de Cachorro e Gato
        AnimalAbstrato cachorro = new Cachorro("Rex");
        AnimalAbstrato gato = new Gato("Miau");

        // Chamando o método fazerSom
        cachorro.fazerSom(); // Saída: Rex faz au au.
        gato.fazerSom();     // Saída: Miau faz miau.
    }
}
Enter fullscreen mode Exit fullscreen mode

Saída Esperada:

Rex faz au au.
Miau faz miau.
Enter fullscreen mode Exit fullscreen mode

Problemas de Herança com Superclasses

1. Dependência Forte entre Subclasse e Superclasse

Quando uma classe herda de outra, ela cria uma dependência direta entre a subclasse e a superclasse. Isso significa que qualquer mudança na superclasse pode impactar todas as subclasses, o que pode tornar o código difícil de manter.

Exemplo:

public class Animal {
    public void mover() {
        System.out.println("O animal está se movendo.");
    }
}

public class Peixe extends Animal {
    @Override
    public void mover() {
        System.out.println("O peixe está nadando.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Se no futuro você alterar a implementação de Animal, por exemplo, mudando o método mover para incluir uma lógica diferente, essa alteração será automaticamente propagada para todas as subclasses, o que pode gerar comportamentos inesperados.

Problema: Manutenção Difícil

Se a superclasse for modificada, todas as subclasses precisam ser testadas novamente para garantir que essas mudanças não quebraram o comportamento esperado.

2. Herança Múltipla

Em Java, uma classe não pode estender mais de uma classe, ou seja, não é possível ter herança múltipla. Isso significa que uma subclasse pode herdar de apenas uma superclasse. Essa restrição ajuda a evitar a complexidade que pode surgir de heranças múltiplas, como a ambiguidade de métodos (por exemplo, se duas superclasses tiverem métodos com o mesmo nome).

Exemplo:

public class A {
    public void metodo() {
        System.out.println("Método da classe A");
    }
}

public class B {
    public void metodo() {
        System.out.println("Método da classe B");
    }
}

// Isso não é permitido em Java
public class C extends A, B { // Erro: não pode estender mais de uma classe
}
Enter fullscreen mode Exit fullscreen mode

3. Polimorfismo e Hierarquia Complexa

Às vezes, a hierarquia de classes pode se tornar muito complexa, dificultando o entendimento do relacionamento entre classes e a previsibilidade do comportamento do código. Isso pode levar a um uso ineficiente de polimorfismo, onde as subclasses não se comportam como esperado.

Considerações Finais

A herança e as classes abstratas são conceitos poderosos na POO que ajudam a criar sistemas mais organizados e reutilizáveis. No entanto, é fundamental usar a herança com cautela, reconhecendo os problemas potenciais que podem surgir, como dependências fortes, a restrição da herança única e a complexidade da hierarquia. Avaliar a necessidade de herança versus outras abordagens, como composição, pode resultar em um design de software mais limpo e sustentável.

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Eliminate Context Switching and Maximize Productivity

Pieces.app

Pieces Copilot is your personalized workflow assistant, working alongside your favorite apps. Ask questions about entire repositories, generate contextualized code, save and reuse useful snippets, and streamline your development process.

Learn more