Explique como a classe Veiculo força suas subclasses (CarroAutonomo, DroneAutonomo, OnibusAutonomo) a implementar os métodos mover() e calcular_eficiencia(). Qual é a vantagem de usar uma classe abstrata nesse contexto em vez de uma classe concreta?

Passo 1

O que o enunciado pede:
  • Explicar como uma classe chamada Veiculo força suas subclasses (CarroAutonomo, DroneAutonomo, OnibusAutonomo) a implementar os métodos mover() e calcular_eficiencia().
  • Dizer qual é a vantagem de usar uma classe abstrata nesse contexto em vez de uma classe concreta.
Informações que temos/assumimos (essas são as peças que vou usar para explicar):
  • Existem três subclasses que representam tipos diferentes de veículo autônomo.
  • Todos os veículos devem ter um método que executa o movimento (mover) e um que calcula a eficiência (calcular_eficiencia).
  • Queremos garantir, por desenho, que TODAS as subclasses forneçam implementações desses métodos.
A resolução começa no próximo passo.

Passo 2

O que é uma classe abstrata (explicação didática, sem assumir conhecimento prévio):
  • Pense numa classe abstrata como um “contrato com comportamento parcial”. Ela declara que certas operações existem, mas não necessariamente diz como elas serão feitas.
  • Dois pontos-chave:
    1. Uma classe abstrata pode declarar métodos “abstratos” (assinatura só, sem corpo). Esses métodos obrigam subclasses a implementar.
    2. Normalmente não é possível criar instâncias diretamente de uma classe abstrata — isso evita que alguém use a classe-base sem fornecer as partes específicas que faltam.
  • Em termos práticos: a classe abstrata Veiculo diz “todo veículo deve saber mover-se e calcular sua eficiência”, mas deixa para cada tipo de veículo (CarroAutonomo, DroneAutonomo, OnibusAutonomo) decidir o detalhe de como isso é feito.

Passo 3

Como a classe abstrata força a implementação (conceito):
  • A classe abstrata declara métodos mover() e calcular_eficiencia() como abstratos (ou “métodos não implementados”).
  • Linguagens com suporte a abstração (Python com abc, Java com abstract, C++ com funções virtuais puras) impõem que qualquer subclasse concreta que queira ser instanciada deve fornecer uma implementação concreta para esses métodos.
  • Se uma subclasse não implementar todos os métodos abstratos, então:
    • Em tempo de compilação (Java, C++), o compilador reclama e impede a compilação.
    • Em tempo de execução (Python), a tentativa de instanciar a subclasse lança um erro informando que ainda há métodos abstratos não implementados.
  • Resultado prático: garantia automática, sem checagens manuais, que todas as classes concretas têm mover() e calcular_eficiencia().

Passo 4

Exemplo em Python (passo a passo, explicando cada linha)
  • Primeiro a ideia: usar o módulo abc para declarar uma classe abstrata.
Código (explicado após):
from abc import ABC, abstractmethod

class Veiculo(ABC):
    @abstractmethod
    def mover(self):
        pass

    @abstractmethod
    def calcular_eficiencia(self) -> float:
        pass

class CarroAutonomo(Veiculo):
    def __init__(self, peso, consumo_km_por_litro):
        self.peso = peso
        self.consumo = consumo_km_por_litro

    def mover(self):
        print("Direcionando rodas e controlando aceleração do carro.")

    def calcular_eficiencia(self) -> float:
        # Exemplo simples: eficiência inversamente proporcional ao peso
        return self.consumo / (1 + self.peso * 0.01)

class DroneAutonomo(Veiculo):
    def __init__(self, bateria_wh, aerodinamica):
        self.bateria = bateria_wh
        self.aero = aerodinamica

    def mover(self):
        print("Ajustando hélices e controle de altitude do drone.")

    def calcular_eficiencia(self) -> float:
        # Exemplo simples: autonomia proporcional à bateria e aerodinâmica
        return self.bateria * self.aero / 100.0

class OnibusAutonomo(Veiculo):
    def __init__(self, passageiros, consumo_por_passageiro):
        self.passageiros = passageiros
        self.consumo_por_passageiro = consumo_por_passageiro

    def mover(self):
        print("Controlando tração e frenagem do ônibus.")

    def calcular_eficiencia(self) -> float:
        # Eficiência por passageiro
        return self.passageiros / (1 + self.consumo_por_passageiro)
Explicação das partes importantes:
  • class Veiculo(ABC): declara Veiculo como abstrata.
  • @abstractmethod marca mover e calcular_eficiencia como métodos que NÃO têm implementação na classe base. Elas são só a assinatura/contrato.
  • Cada subclasse fornece um def mover(self): e um def calcular_eficiencia(self) -> float: com comportamento concreto.
  • Se faltar implementar um desses métodos em alguma subclasse e você tentar criar uma instância dessa subclasse, o Python levanta TypeError: Can't instantiate abstract class X with abstract methods .... Isso demonstra a “força” da classe abstrata: a linguagem impede a criação de objetos incompletos.

Passo 5

O que acontece se esquecer de implementar um método (exemplo prático)
  • Exemplo: se class DroneAutonomo(Veiculo): não definir calcular_eficiencia, então:
    • Em Python: ao fazer d = DroneAutonomo(...) recebe-se TypeError dizendo que a classe é abstrata porque falta implementar calcular_eficiencia.
    • Em Java: a classe não compilaria; o compilador exige @Override do método abstrato ou que a subclasse seja também declarada abstract.
    • Em C++: a subclasse que não implementa todas as funções virtuais puras continua sendo abstrata; não dá pra instanciar.
  • Conclusão prática: evita bugs do tipo “esqueci de implementar algo importante” porque o próprio ambiente (compilador/interpreter) aponta o erro.

Passo 6

Exemplo curto em Java (para comparar o comportamento em linguagens compiladas)
  • Esquema:
public abstract class Veiculo {
    public abstract void mover();
    public abstract double calcularEficiencia();
}

public class CarroAutonomo extends Veiculo {
    @Override
    public void mover() { System.out.println("Movendo carro"); }
    @Override
    public double calcularEficiencia() { return 12.5; }
}
  • Em Java, se CarroAutonomo não implementar os dois métodos, o compilador exige que CarroAutonomo seja declarado abstract também — caso contrário dá erro de compilação. Ou seja, regra aplicada em tempo de compilação.

Passo 7

Polimorfismo e uso prático na prova/na vida: por que isso importa
  • Depois que todas as subclasses implementam os métodos exigidos, você pode tratar instâncias como Veiculo e chamar mover() ou calcular_eficiencia() sem saber o tipo concreto.
  • Exemplo em Python:
frota = [CarroAutonomo(...), DroneAutonomo(...), OnibusAutonomo(...)]
for v in frota:
    v.mover()                      # despacho dinâmico: chama o mover correto para cada tipo
    print(v.calcular_eficiencia()) # idem para calcular_eficiencia
  • Vantagem prática: código do cliente (quem usa veículos) não precisa de if/elif para saber o tipo de veículo — cada classe sabe como executar suas operações.

Passo 8

Vantagens de usar uma classe abstrata (resumindo, com detalhes e exemplos de aplicação)
  • Garantia de contrato: obriga que os métodos essenciais existam — evita que alguém esqueça de implementar.
  • Verificação cedo: erro é detectado pelo compilador/interpreter na criação da classe/instância, não em runtime de forma silenciosa.
  • Reuso de código comum: a classe abstrata pode ter atributos e métodos concretos (por exemplo, id, velocidade_max, método auxiliar de log) que são reutilizados por todas as subclasses, evitando duplicação.
  • Documentação implícita: a classe abstrata documenta o comportamento mínimo esperado de qualquer Veiculo.
  • Flexibilidade para fornecer default: se quiser, a classe abstrata pode fornecer implementação padrão de um método (não abstrato) que subclasses podem usar ou sobrescrever.
  • Polimorfismo seguro: permite escrever código genérico que opera sobre Veiculo de forma segura.
  • Comparação com classe concreta: se Veiculo fosse concreta com métodos vazios (implementações “fakes”), nada impediria uma subclasse de herdar o método vazio e esquecer de implementar o real — o erro só apareceria em runtime quando o método fosse chamado (e talvez nem falasse nada). A classe abstrata evita esse problema ao tornar a implementação obrigatória.

Passo 9

Quando preferir classe abstrata a interface (observação prática)
  • Use classe abstrata quando quiser:
    • Fornecer estado (atributos) compartilhado ou comportamento parcial.
    • Ter implementações padrão além das assinaturas.
  • Use interface (ou equivalente) quando quiser apenas um contrato puro (somente assinaturas), especialmente se a linguagem permite múltipla herança de interfaces mas não de classes.
  • No caso Veiculo: se existe comportamento ou dados comuns (ex.: id, capacidade, métodos de validação), uma classe abstrata é ótima. Se for só um contrato sem nada em comum, uma interface também resolve.

Passo 10

Dicas rápidas para responder isso numa prova (formule uma resposta curta e completa)
  • Diga: “Veiculo é abstrata e declara mover() e calcular_eficiencia() como abstratos; qualquer subclasse concreta deve sobrescrevê-los, caso contrário não pode ser instanciada”.
  • Acrescente um mini exemplo (2–4 linhas) ou nome da anotação/keyword na linguagem da prova (por exemplo, @abstractmethod em Python, abstract em Java, =0 em C++).
  • Aponte uma vantagem chave: “força um contrato em tempo de compilação/instanciação e permite polimorfismo seguro”.

Resposta

Como: a classe abstrata Veiculo declara os métodos mover() e calcular_eficiencia() como métodos abstratos (por exemplo usando @abstractmethod em Python, abstract em Java ou funções virtuais puras em C++). Linguagens que suportam abstração impedem a instanciação de classes que não implementem esses métodos, obrigando CarroAutonomo, DroneAutonomo e OnibusAutonomo a fornecer implementações concretas antes de poderem ser instanciadas.
Vantagem: usar uma classe abstrata garante em tempo de compilação/instanciação que todas as subclasses implementem a interface mínima (contrato), possibilita reutilizar código/estado comum na base, e permite polimorfismo seguro — ao contrário de uma classe concreta com métodos vazios, que pode permitir esqueletos incompletos e causar erros silenciosos em runtime.

Exercícios relacionados