Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
1
resposta

[Projeto] Faça como eu fiz: simulador de transporte urbano

Classe Program:

List<Transporte> opcoes = new()
{
    new Onibus(),
    new Metro(),
    new Bicicleta()
};

int distancia = 10;

foreach (var transporte in opcoes)
{
    Console.WriteLine($"{transporte.GetType().Name}: {transporte.CalcularTempo(distancia)} min");
}

Classe Transporte:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal class Transporte
{
    public virtual int CalcularTempo(int distanciaKm)
    {
        return 0;
    }
}

Classe Onibus:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal class Onibus : Transporte
{
    public override int CalcularTempo(int distanciaKm)
    {
        return (distanciaKm * 2) + 5;
    }
}

Classe Metro:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal class Metro : Transporte
{
    public override int CalcularTempo(int distanciaKm)
    {
        return distanciaKm + 5;
    }
}

Classe Bicicleta:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal class Bicicleta : Transporte
{
    public override int CalcularTempo(int distanciaKm)
    {
        return distanciaKm * 4;
    }
}
1 resposta
solução!

Olá, Fabiano. Como vai?

Parabéns pela implementação do desafio! O seu código demonstra perfeitamente como o Polimorfismo de Subtipos funciona na prática.

Ao criar uma lista genérica do tipo List<Transporte> e preenchê-la com instâncias de Onibus, Metro e Bicicleta, você aplicou o conceito de herança de forma muito eficiente. No laço foreach, quando o método CalcularTempo é chamado, o C# identifica em tempo de execução qual é a classe real do objeto e executa o comportamento customizado que você definiu com o override. Isso é a essência do polimorfismo: tratar objetos diferentes de maneira uniforme através de uma classe base comum.

Para agregar ainda mais valor ao seu projeto e pensando em boas práticas de arquitetura de software, quero compartilhar duas sugestões de melhorias que podem deixar o seu código ainda mais robusto:

1. Transformar a classe base em abstract

Atualmente, a sua classe Transporte possui um método virtual que retorna 0. No entanto, no mundo real, um transporte genérico não calcula tempo, apenas os transportes específicos fazem isso.

Se transformarmos a classe Transporte em uma classe abstrata (abstract), impedimos que alguém crie um objeto diretamente dela (como new Transporte()), o que não faria muito sentido no sistema. Além disso, podemos definir o método CalcularTempo como um método abstrato, obrigando qualquer nova classe que herdar de Transporte a implementar sua própria lógica de cálculo.

Veja como ficaria a classe base modificada:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal abstract class Transporte
{
    // Métodos abstratos não possuem corpo, apenas a assinatura
    public abstract int CalcularTempo(int distanciaKm);
}

Dessa forma, o C# vai te ajudar a não esquecer de implementar o cálculo caso você decida adicionar um novo meio de transporte no futuro, como um Carro ou Patrinete.


2. Evitar o uso de "Números Mágicos"

No código das classes filhas, você utilizou alguns valores fixos para fazer o cálculo, como o multiplicador e o tempo fixo de espera (ex: * 2 e + 5). Na programação, chamamos isso de números mágicos, pois quem lê o código pode não entender de imediato o que eles significam.

Uma boa prática é documentar ou transformar esses valores em propriedades ou constantes com nomes claros dentro da própria classe. Veja o exemplo de como a classe Onibus poderia ser escrita:

namespace Polimorfismo.SimuladorTransporteUrbano;

internal class Onibus : Transporte
{
    // Definição clara dos fatores que afetam o tempo
    private const int MinutosPorQuilometro = 2;
    private const int TempoFixoEsperaParada = 5;

    public override int CalcularTempo(int distanciaKm)
    {
        return (distanciaKm * MinutosPorQuilometro) + TempoFixoEsperaParada;
    }
}

Isso torna o seu código muito mais legível, limpo e fácil de manter caso as regras do simulador mudem depois!

Seu projeto ficou excelente e a estrutura principal está impecável. Continue praticando e aplicando esses conceitos de Orientação a Objetos.

Espero que possa ter lhe ajudado!