Módulo 11 - Orientação a objetos: herança

Prática: hierarquia de formas

12 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Montar uma classe base Forma com o método area().
  • Criar Retangulo e Circulo sobrescrevendo area() cada uma.
  • Somar as áreas de uma lista de formas com um único laço.
  • Confirmar que um novo tipo de forma entra sem mudar o laço.

A classe base Forma

Esta aula fecha a unidade juntando os três pilares que você viu: herança, sobrescrita e polimorfismo. O exemplo é clássico porque é limpo: formas geométricas. Toda forma tem uma área, mas cada tipo calcula a sua de um jeito. O retângulo multiplica base por altura; o círculo usa pi vezes o raio ao quadrado. Em vez de espalhar essas fórmulas por ifs, cada forma cuida da sua. Começamos pela base Forma, que declara que toda forma deve saber calcular a própria área, mesmo que ela mesma não saiba qual é a forma.

class Forma:
    def __init__(self, nome):
        self.nome = nome

    def area(self):
        # a base não sabe a fórmula; as filhas devem sobrescrever
        raise NotImplementedError("Cada forma deve calcular a sua área.")

    def descrever(self):
        return f"{self.nome}: área de {self.area():.2f}"

Forma define o contrato area() e um descrever() que já usa area().

Repare em duas decisões. A primeira: area() na base levanta NotImplementedError, um jeito educado de dizer eu, Forma genérica, não tenho fórmula; quem herdar de mim precisa fornecer a sua. Se alguém esquecer de sobrescrever area() numa filha, o erro avisa na hora, com uma mensagem clara. A segunda: descrever() já chama self.area() sem saber qual forma é. Ele confia no contrato. Quando um retângulo executar descrever(), o self.area() de lá dentro vai rodar a fórmula do retângulo, pela sobrescrita. A base escreve o texto uma vez, e serve a todas.

As filhas Retangulo e Circulo

Agora as formas concretas. Cada uma herda de Forma, estende o construtor com super para guardar o nome e acrescenta os seus dados próprios: o retângulo tem base e altura; o círculo tem raio. E cada uma sobrescreve area() com a sua fórmula. É a soma de tudo o que a unidade ensinou: a sintaxe da classe filha, o super no construtor e a sobrescrita do método. Repare como cada classe fica pequena e focada, contando só a sua parte da história.

import math

class Retangulo(Forma):
    def __init__(self, base, altura):
        super().__init__("Retângulo")
        self.base = base
        self.altura = altura

    def area(self):
        return self.base * self.altura

class Circulo(Forma):
    def __init__(self, raio):
        super().__init__("Círculo")
        self.raio = raio

    def area(self):
        return math.pi * self.raio ** 2

r = Retangulo(4, 3)
c = Circulo(5)
print(r.descrever())  # Retângulo: área de 12.00
print(c.descrever())  # Círculo: área de 78.54

Retangulo e Circulo herdam, chamam super e sobrescrevem area(); descrever() vem da base.

Veja o que aconteceu com descrever(). Nenhuma das filhas o escreveu; ele veio inteiro de Forma. Ainda assim, r.descrever() mostra a área do retângulo e c.descrever() a do círculo, cada um com a sua fórmula, porque o self.area() lá dentro da base resolve para a versão da filha na hora da chamada. É o polimorfismo trabalhando dentro de um método herdado. A base cuidou do texto e da formatação uma vez só; as filhas cuidaram só das fórmulas. Cada responsabilidade num lugar só.

Somando as áreas com polimorfismo

O momento que amarra tudo: uma lista com formas de tipos diferentes e um único laço que soma as áreas. O laço não pergunta se o item é retângulo ou círculo; ele só chama area() e vai somando. Cada forma responde com a sua fórmula, e o total sai certo. Esse é o padrão que você vai reencontrar em muitos programas de verdade: uma coleção de objetos variados, tratada de forma uniforme por uma interface comum. Aqui a interface é o método area().

formas = [Retangulo(4, 3), Circulo(5), Retangulo(2, 2)]

total = 0
for forma in formas:
    total += forma.area()   # cada forma usa a sua própria fórmula

print(f"São {len(formas)} formas.")      # São 3 formas.
print(f"Área total: {total:.2f}")        # Área total: 94.54

# também dá para usar sum() com uma compreensão:
total2 = sum(f.area() for f in formas)
print(f"{total2:.2f}")                    # 94.54

Um laço soma as áreas de formas variadas; nenhuma checagem de tipo, só area().

Some na cabeça para conferir: doze do primeiro retângulo, mais cerca de 78,54 do círculo, mais quatro do segundo retângulo, dá 94,54. O laço chegou lá sem saber nada sobre formas específicas. E, como você já viu na aula de polimorfismo, criar um Triangulo que herde de Forma e sobrescreva area() é suficiente para ele entrar na lista e ser somado, sem tocar no laço. Herança colocou o comum na base, a sobrescrita deu a cada forma a sua fórmula, e o polimorfismo deixou o laço genérico e à prova de novos tipos. Este é o retrato da orientação a objetos bem usada.

Teste rápido

Por que o laço que faz total += forma.area() funciona com retângulos e círculos misturados?

Perguntas frequentes

Por que a área na base levanta NotImplementedError?
Porque Forma é genérica e não tem fórmula própria; ela só define o contrato de que toda forma calcula area(). Levantar NotImplementedError avisa na hora, com mensagem clara, se alguém criar uma forma e esquecer de sobrescrever area(). É um sinal amigável de contrato não cumprido.
O método descrever() precisa ser reescrito em cada forma?
Não. Ele mora só na base Forma e é herdado por todas. Como ele chama self.area(), o polimorfismo faz cada forma usar a sua fórmula ao descrever. Escrever o texto uma vez na base e deixar as filhas cuidarem só das fórmulas é exatamente o ganho da herança bem usada.
Como eu adiciono um triângulo a essa hierarquia?
Crie class Triangulo(Forma), chame super().__init__("Triângulo") no construtor, guarde base e altura e sobrescreva area() com base vezes altura dividido por dois. Coloque um triângulo na lista e o laço de soma já o trata, sem nenhuma alteração. É a prova do polimorfismo extensível.
Por que usar math.pi em vez de digitar 3.14?
math.pi traz o valor de pi com muitas casas decimais, mais preciso que 3.14. Importar da biblioteca padrão evita erros de digitação e deixa a intenção clara. É o mesmo espírito de reaproveitar código pronto e testado que o curso reforça desde o começo.
Dá para somar as áreas sem escrever um laço explícito?
Dá, com sum(f.area() for f in formas). É a versão pythônica: uma compreensão gera as áreas e sum() as soma numa linha. O resultado é o mesmo do laço, mais curto e legível. Os dois caminhos dependem do mesmo polimorfismo: cada forma respondendo a area() do seu jeito.
Essa hierarquia de formas serve para algo real?
O padrão sim, e muito. Programas de desenho, jogos e relatórios lidam o tempo todo com coleções de objetos variados tratados por uma interface comum. Trocar formas por itens de um carrinho, cada um com um preco(), ou por relatórios, cada um com um gerar(), é a mesma estrutura que você acabou de montar.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.