CodeGym /Cursos /JAVA 25 SELF /Implementação de abstrações e hierarquias

Implementação de abstrações e hierarquias

JAVA 25 SELF
Nível 19 , Lição 2
Disponível

1. Construindo a hierarquia: da abstração aos detalhes

Implementar abstrações e hierarquias — é uma forma de estruturar o código do geral para o específico. Primeiro descrevemos o que todos os objetos devem saber fazer (abstração) e, depois, detalhamos como cada classe concreta faz isso.

Na programação, como na vida, tudo começa com perguntas. Por exemplo: “O que há em comum entre um círculo e um retângulo?” Resposta: ambos são figuras. E o que há em comum entre figuras? Normalmente elas têm área e podem ser desenhadas.

Em Java, isso se expressa por meio de uma abstract-class:

public abstract class Shape {
    public abstract double area();
    public abstract void draw();
}

Aqui estamos afirmando:

  • Toda figura deve ser capaz de calcular sua área (area()).
  • Toda figura deve ser capaz de ser desenhada (draw()).
  • Como exatamente ela faz isso — não é da nossa conta (por enquanto).

Agora vamos criar figuras concretas:

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Desenhando um círculo com raio " + radius);
    }
}

public class Rectangle extends Shape {
    private double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("Desenhando um retângulo " + width + "x" + height);
    }
}

O que fizemos?

  • Extraímos o comum para a classe abstrata.
  • Detalhamos o comportamento nas subclasses.

Esquematicamente:


.          Shape
         /     \
     Circle   Rectangle

Tabela: o que é implementado onde

Classe area() draw() Campos próprios
Shape
abstract
abstract
-
Circle implementado implementado
radius
Rectangle implementado implementado
width, height

2. Por que isso é conveniente? (e por que isso funciona)

Interface única para trabalhar com objetos diferentes

Suponha que você tenha uma coleção de figuras:

Shape[] shapes = {
    new Circle(5),
    new Rectangle(3, 4),
    new Circle(2.5)
};

Você pode percorrê-las do mesmo jeito, sem pensar no tipo:

for (Shape shape : shapes) {
    shape.draw();
    System.out.println("Área: " + shape.area());
}

Deixe a JVM se virar para descobrir quem é círculo e quem é retângulo! É isso que chamamos de polimorfismo (já falamos sobre ele e ainda falaremos com mais detalhes — no próximo bloco).

Facilidade de extensão

Quer adicionar um triângulo? Basta escrever (tipo novo — código antigo intocado): Triangle extends Shape.

public class Triangle extends Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }

    @Override
    public void draw() {
        System.out.println("Desenhando triângulo: base " + base + ", altura " + height);
    }
}

O restante do código (por exemplo, o loop que percorre a lista de figuras) não precisa ser alterado.

Evitando duplicação de código

Se todas as figuras passarem a ter uma propriedade comum (por exemplo, cor), é conveniente extraí-la para a classe abstrata:

public abstract class Shape {
    private String color = "preto";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract double area();
    public abstract void draw();
}

Agora qualquer subclasse — seja um círculo ou um triângulo — herdará a cor.

3. Prática: desenvolvendo um mini editor gráfico

Vamos juntar tudo. Imagine que você está criando um editor gráfico simples.

Classe abstrata Figure

public abstract class Figure {
    private String color = "black";

    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }

    public abstract void draw();
    public abstract void resize(double factor);
}

Figuras concretas

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("Desenhando linha de comprimento " + length + " com a cor " + getColor());
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("Novo comprimento da linha: " + length);
    }
}

public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("Desenhando elipse com eixos " + a + " e " + b + " com a cor " + getColor());
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("Novos eixos da elipse: " + a + ", " + b);
    }
}

Quer adicionar uma nova ferramenta, como Polygon? Basta criar uma nova classe — todo o editor trabalha via a classe abstrata Figure.

Uso no código

Figure[] figures = {
    new Line(10),
    new Ellipse(5, 3)
};

for (Figure figure : figures) {
    figure.setColor("red");
    figure.draw();
    figure.resize(1.5);
}

Saída:

Desenhando linha de comprimento 10.0 com a cor red
Novo comprimento da linha: 15.0
Desenhando elipse com eixos 5.0 e 3.0 com a cor red
Novos eixos da elipse: 7.5, 4.5

Visualização da hierarquia


.            Figure
             /    \
          Line   Ellipse

4. Como evitar duplicação: campos e métodos comuns

Às vezes, todas as subclasses têm não só métodos comuns, mas também campos comuns (por exemplo, as coordenadas do centro). A classe abstrata é o lugar ideal para isso:

public abstract class Figure {
    private double x, y; // coordenadas do centro

    public Figure(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public void moveTo(double newX, double newY) {
        x = newX;
        y = newY;
        System.out.println("Figura movida para o ponto (" + x + ", " + y + ")");
    }

    public abstract void draw();
}

Agora quaisquer Line ou Ellipse podem se mover sem reimplementar esse método.

5. Mais um exemplo: sistemas de pagamento

Abstração não é só sobre figuras! Imagine que você está criando um sistema de processamento de pagamentos.

Classe abstrata Payment

public abstract class Payment {
    public abstract void process();
}

Implementações concretas

public class CreditCardPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Processando pagamento com cartão de crédito");
    }
}

public class PaypalPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Processando pagamento via PayPal");
    }
}

Uso

Payment[] payments = {
    new CreditCardPayment(),
    new PaypalPayment()
};

for (Payment payment : payments) {
    payment.process();
}

Saída:

Processando pagamento com cartão de crédito
Processando pagamento via PayPal

6. Vantagens dessa abordagem

  • Interface única: é possível trabalhar com objetos diferentes do mesmo jeito.
  • Extensibilidade: adicionar novos tipos de objetos não exige reescrever o código antigo.
  • Mínima duplicação: o comum fica na classe base abstrata.
  • Flexibilidade: é possível usar coleções de tipos abstratos sem se preocupar com detalhes.

7. Exemplo do mundo real: transporte

A abstração aparece não só nos livros. Por exemplo, se você estiver projetando um sistema para gerenciar transportes:

public abstract class Transport {
    public abstract void move();
    public abstract void fuelUp();
}

Tipos concretos de transporte implementam os detalhes:

public class Car extends Transport {
    @Override
    public void move() {
        System.out.println("O carro anda na estrada");
    }

    @Override
    public void fuelUp() {
        System.out.println("Abastecendo com gasolina");
    }
}

public class Bicycle extends Transport {
    @Override
    public void move() {
        System.out.println("A bicicleta pedala");
    }

    @Override
    public void fuelUp() {
        System.out.println("A bicicleta não precisa de combustível, só de sanduíches para o ciclista!");
    }
}

8. Esquema útil: como construir uma hierarquia de abstrações

[Classe abstrata]
        |
   [Subclasse concreta]
        |
   [Subclasse ainda mais específica] (se necessário)

- Tudo que é comum — no topo!
- Tudo que é único — embaixo!

9. Erros comuns na implementação de abstrações e hierarquias

Erro nº 1: Duplicação de código nas subclasses.
Se você notar que em cada subclasse está escrevendo os mesmos campos ou métodos — é um sinal de que vale a pena extraí-los para a classe abstrata. Não tenha medo de tornar a abstração “mais ampla” se isso reduzir a duplicação.

Erro nº 2: Violação do princípio “do geral para o específico”.
Às vezes iniciantes começam a construir a hierarquia “pelos detalhes”, esquecendo do que é comum. Como resultado, surgem classes estranhas como RedCircleWithShadow, que não se encaixam bem na estrutura geral. Sempre destaque a abstração primeiro e depois os detalhes.

Erro nº 3: Hierarquias profundas demais.
Se sua cadeia de herança tiver mais de 3–4 níveis, pense se não é hora de usar composição ou interfaces em vez de herança.

Erro nº 4: Forçar a implementação de métodos não pertinentes.
Se a classe abstrata contém muitos métodos abstratos que não fazem sentido para alguns herdeiros, talvez seja hora de revisar a estrutura. Por exemplo, nem todo meio de transporte precisa de um método fuelUp() (não faz sentido para uma bicicleta).

Erro nº 5: Confundir classe abstrata com interface.
Classe abstrata — quando há estado comum e/ou implementação parcial. Interface — quando é preciso apenas “prometer” a existência de métodos, sem armazenar dados ou implementar comportamento. Não misture essas abordagens sem necessidade.

1
Tarefa
JAVA 25 SELF, nível 19, lição 2
Bloqueado
Simulador de transporte: carro em movimento 🛣️
Simulador de transporte: carro em movimento 🛣️
1
Tarefa
JAVA 25 SELF, nível 19, lição 2
Bloqueado
Biblioteca de formas geométricas 🔵⬛
Biblioteca de formas geométricas 🔵⬛
1
Tarefa
JAVA 25 SELF, nível 19, lição 2
Bloqueado
Sistema básico de gerenciamento de pessoal 🧑‍💼
Sistema básico de gerenciamento de pessoal 🧑‍💼
1
Tarefa
JAVA 25 SELF, nível 19, lição 2
Bloqueado
Sistema de pagamento flexível na loja 💰
Sistema de pagamento flexível na loja 💰
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION