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!