Ir al contenido principal

Polimorfismo y métodos virtuales

El polimorfismo representa uno de los conceptos más elegantes y poderosos de la programación orientada a objetos, permitiendo que objetos de diferentes tipos respondan de manera específica a la misma interfaz común. En C#, este mecanismo se implementa principalmente a través de métodos virtuales, proporcionando flexibilidad y extensibilidad a las aplicaciones mientras mantiene un código limpio y mantenible.

Este concepto permite que una misma operación se comporte de manera diferente según el tipo específico del objeto que la ejecuta, sin que el código cliente necesite conocer los detalles de implementación. Esta capacidad es fundamental para crear sistemas flexibles que pueden adaptarse y extenderse fácilmente a medida que evolucionan los requisitos.

La implementación del polimorfismo en C# se basa en la combinación de herencia, métodos virtuales y sobrescritura, creando un sistema que permite tanto la reutilización de código como la especialización de comportamientos según las necesidades específicas de cada clase derivada.

Fundamentos teóricos del polimorfismo

Definición formal de polimorfismo

El polimorfismo es la capacidad de un objeto de tomar múltiples formas y comportarse de manera diferente según su tipo específico, manteniendo una interfaz común. En C#, esto significa que una referencia de tipo base puede apuntar a objetos de tipos derivados, y cada objeto responderá según su implementación específica.

Tipos de polimorfismo en C#

Tipo Descripción Mecanismo Momento de resolución
Polimorfismo en tiempo de compilación Sobrecarga de métodos y operadores Métodos con diferentes parámetros Compilación
Polimorfismo en tiempo de ejecución Sobrescritura de métodos virtuales virtual y override Ejecución
Polimorfismo de interfaz Implementación de interfaces Contratos de interface Ejecución
Polimorfismo genérico Uso de tipos genéricos Parámetros de tipo Compilación/Ejecución

Conceptos fundamentales

Concepto Definición Palabra clave
Método virtual Método que puede ser sobrescrito en clases derivadas virtual
Sobrescritura Redefinición de un método virtual en una clase derivada override
Enlace tardío Resolución del método a ejecutar en tiempo de ejecución Automático
Método sellado Método que no puede ser sobrescrito posteriormente sealed

Métodos virtuales en C#

Declaración y uso de métodos virtuales

Los métodos virtuales proporcionan la base para el polimorfismo en tiempo de ejecución. Se declaran con la palabra clave virtual en la clase base y pueden ser sobrescritos en las clases derivadas usando override.

// Clase base con métodos virtuales
public class Forma
{
    public string Nombre { get; protected set; }
    public string Color { get; set; }
    
    public Forma(string nombre)
    {
        Nombre = nombre;
        Color = "Transparente";
    }
    
    // Método virtual que puede ser sobrescrito
    public virtual double CalcularArea()
    {
        Console.WriteLine($"Calculando área de forma genérica: {Nombre}");
        return 0.0;
    }
    
    // Método virtual para mostrar información
    public virtual void MostrarInformacion()
    {
        Console.WriteLine($"Forma: {Nombre}");
        Console.WriteLine($"Color: {Color}");
        Console.WriteLine($"Área: {CalcularArea():F2}");
    }
    
    // Método virtual para dibujar
    public virtual void Dibujar()
    {
        Console.WriteLine($"Dibujando una forma genérica: {Nombre}");
    }
}

Sobrescritura de métodos virtuales

// Clase derivada: Círculo
public class Circulo : Forma
{
    public double Radio { get; set; }
    
    public Circulo(double radio) : base("Círculo")
    {
        Radio = radio;
    }
    
    // Sobrescritura del método virtual
    public override double CalcularArea()
    {
        Console.WriteLine($"Calculando área del círculo con radio {Radio}");
        return Math.PI * Radio * Radio;
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion(); // Llama al método base
        Console.WriteLine($"Radio: {Radio:F2}");
    }
    
    public override void Dibujar()
    {
        Console.WriteLine($"Dibujando un círculo de radio {Radio} en color {Color}");
    }
}

// Clase derivada: Rectángulo
public class Rectangulo : Forma
{
    public double Ancho { get; set; }
    public double Alto { get; set; }
    
    public Rectangulo(double ancho, double alto) : base("Rectángulo")
    {
        Ancho = ancho;
        Alto = alto;
    }
    
    public override double CalcularArea()
    {
        Console.WriteLine($"Calculando área del rectángulo {Ancho}x{Alto}");
        return Ancho * Alto;
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Dimensiones: {Ancho:F2} x {Alto:F2}");
    }
    
    public override void Dibujar()
    {
        Console.WriteLine($"Dibujando un rectángulo de {Ancho}x{Alto} en color {Color}");
    }
}

// Clase derivada: Triángulo
public class Triangulo : Forma
{
    public double Base { get; set; }
    public double Altura { get; set; }
    
    public Triangulo(double baseTriangulo, double altura) : base("Triángulo")
    {
        Base = baseTriangulo;
        Altura = altura;
    }
    
    public override double CalcularArea()
    {
        Console.WriteLine($"Calculando área del triángulo con base {Base} y altura {Altura}");
        return (Base * Altura) / 2;
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Base: {Base:F2}, Altura: {Altura:F2}");
    }
    
    public override void Dibujar()
    {
        Console.WriteLine($"Dibujando un triángulo de base {Base} y altura {Altura} en color {Color}");
    }
}

Polimorfismo en acción

Demostración básica del polimorfismo

class Program
{
    static void Main()
    {
        // Creación de un array polimórfico
        Forma[] formas = {
            new Circulo(5.0) { Color = "Rojo" },
            new Rectangulo(4.0, 6.0) { Color = "Azul" },
            new Triangulo(8.0, 3.0) { Color = "Verde" },
            new Circulo(2.5) { Color = "Amarillo" }
        };
        
        Console.WriteLine("=== Demostración de Polimorfismo ===\n");
        
        // El polimorfismo permite tratar todos los objetos de manera uniforme
        foreach (Forma forma in formas)
        {
            // Cada objeto responde según su tipo específico
            forma.MostrarInformacion();
            forma.Dibujar();
            Console.WriteLine($"Área calculada: {forma.CalcularArea():F2}");
            Console.WriteLine(new string('-', 40));
        }
    }
}

Métodos que trabajan con polimorfismo

public class ProcesadorFormas
{
    // Método que acepta cualquier forma (polimorfismo)
    public static void ProcesarForma(Forma forma)
    {
        Console.WriteLine($"Procesando: {forma.Nombre}");
        forma.Dibujar();
        
        double area = forma.CalcularArea();
        if (area > 50)
        {
            Console.WriteLine("¡Forma grande detectada!");
        }
        else if (area > 20)
        {
            Console.WriteLine("Forma de tamaño medio");
        }
        else
        {
            Console.WriteLine("Forma pequeña");
        }
    }
    
    // Método para calcular área total de una colección
    public static double CalcularAreaTotal(IEnumerable<Forma> formas)
    {
        double total = 0;
        foreach (Forma forma in formas)
        {
            total += forma.CalcularArea(); // Polimorfismo en acción
        }
        return total;
    }
    
    // Método para filtrar formas por área mínima
    public static List<Forma> FiltrarPorArea(IEnumerable<Forma> formas, double areaMinima)
    {
        var formasFiltradas = new List<Forma>();
        
        foreach (Forma forma in formas)
        {
            if (forma.CalcularArea() >= areaMinima)
            {
                formasFiltradas.Add(forma);
            }
        }
        
        return formasFiltradas;
    }
}

Métodos sellados (sealed)

Prevenir sobrescritura adicional

La palabra clave sealed permite prevenir que un método sobrescrito sea sobrescrito nuevamente en clases derivadas posteriores.

// Clase intermedia que sella ciertos métodos
public class FormaEspecializada : Forma
{
    protected string TipoEspecializacion { get; set; }
    
    public FormaEspecializada(string nombre, string especializacion) : base(nombre)
    {
        TipoEspecializacion = especializacion;
    }
    
    // Método sellado - no puede ser sobrescrito en clases derivadas
    public sealed override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Especialización: {TipoEspecializacion}");
        Console.WriteLine("--- Información especializada ---");
    }
    
    // Este método aún puede ser sobrescrito
    public override void Dibujar()
    {
        Console.WriteLine($"Dibujando forma especializada: {Nombre} ({TipoEspecializacion})");
    }
}

public class CirculoEspecial : FormaEspecializada
{
    public double Radio { get; set; }
    
    public CirculoEspecial(double radio) : base("Círculo Especial", "Geometría Avanzada")
    {
        Radio = radio;
    }
    
    // ✅ Permitido - método no sellado
    public override void Dibujar()
    {
        Console.WriteLine($"Dibujando círculo especial con efectos 3D, radio: {Radio}");
    }
    
    // ❌ Error de compilación - método sellado
    // public override void MostrarInformacion() { }
    
    public override double CalcularArea()
    {
        return Math.PI * Radio * Radio;
    }
}

Polimorfismo avanzado

Sistema de procesamiento de documentos

// Clase base para documentos
public abstract class Documento
{
    public string Titulo { get; set; }
    public string Autor { get; set; }
    public DateTime FechaCreacion { get; set; }
    
    protected Documento(string titulo, string autor)
    {
        Titulo = titulo;
        Autor = autor;
        FechaCreacion = DateTime.Now;
    }
    
    // Método virtual para abrir el documento
    public virtual void Abrir()
    {
        Console.WriteLine($"Abriendo documento: {Titulo}");
    }
    
    // Método virtual para guardar
    public virtual void Guardar()
    {
        Console.WriteLine($"Guardando documento: {Titulo}");
    }
    
    // Método abstracto que debe ser implementado por las clases derivadas
    public abstract void Procesar();
    
    // Método virtual para mostrar información
    public virtual void MostrarInformacion()
    {
        Console.WriteLine($"Título: {Titulo}");
        Console.WriteLine($"Autor: {Autor}");
        Console.WriteLine($"Fecha: {FechaCreacion:dd/MM/yyyy HH:mm}");
    }
}

// Documento de texto
public class DocumentoTexto : Documento
{
    public int NumeroPalabras { get; set; }
    public string Formato { get; set; }
    
    public DocumentoTexto(string titulo, string autor) : base(titulo, autor)
    {
        Formato = "TXT";
    }
    
    public override void Abrir()
    {
        base.Abrir();
        Console.WriteLine("Iniciando editor de texto...");
    }
    
    public override void Procesar()
    {
        Console.WriteLine($"Procesando texto: contando palabras...");
        // Simulación de procesamiento
        NumeroPalabras = new Random().Next(100, 1000);
        Console.WriteLine($"Texto procesado: {NumeroPalabras} palabras encontradas");
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Formato: {Formato}");
        Console.WriteLine($"Palabras: {NumeroPalabras}");
    }
}

// Documento de imagen
public class DocumentoImagen : Documento
{
    public int Ancho { get; set; }
    public int Alto { get; set; }
    public string FormatoImagen { get; set; }
    
    public DocumentoImagen(string titulo, string autor, int ancho, int alto) : base(titulo, autor)
    {
        Ancho = ancho;
        Alto = alto;
        FormatoImagen = "JPG";
    }
    
    public override void Abrir()
    {
        base.Abrir();
        Console.WriteLine("Iniciando visor de imágenes...");
    }
    
    public override void Procesar()
    {
        Console.WriteLine($"Procesando imagen {Ancho}x{Alto}...");
        Console.WriteLine("Aplicando filtros y optimizaciones...");
        Console.WriteLine("Imagen procesada correctamente");
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Resolución: {Ancho}x{Alto}");
        Console.WriteLine($"Formato: {FormatoImagen}");
    }
}

// Documento PDF
public class DocumentoPDF : Documento
{
    public int NumeroPaginas { get; set; }
    public bool TieneSeguridad { get; set; }
    
    public DocumentoPDF(string titulo, string autor, int paginas) : base(titulo, autor)
    {
        NumeroPaginas = paginas;
        TieneSeguridad = false;
    }
    
    public override void Abrir()
    {
        base.Abrir();
        if (TieneSeguridad)
        {
            Console.WriteLine("Verificando credenciales de seguridad...");
        }
        Console.WriteLine("Iniciando lector PDF...");
    }
    
    public override void Procesar()
    {
        Console.WriteLine($"Procesando PDF de {NumeroPaginas} páginas...");
        Console.WriteLine("Extrayendo texto y metadatos...");
        Console.WriteLine("Generando índice de contenido...");
        Console.WriteLine("PDF procesado correctamente");
    }
    
    public override void MostrarInformacion()
    {
        base.MostrarInformacion();
        Console.WriteLine($"Páginas: {NumeroPaginas}");
        Console.WriteLine($"Seguridad: {(TieneSeguridad ? "Activada" : "No activada")}");
    }
}

Procesador polimórfico de documentos

public class ProcesadorDocumentos
{
    private List<Documento> documentos;
    
    public ProcesadorDocumentos()
    {
        documentos = new List<Documento>();
    }
    
    public void AgregarDocumento(Documento documento)
    {
        documentos.Add(documento);
        Console.WriteLine($"Documento agregado: {documento.Titulo}");
    }
    
    // Método polimórfico que funciona con cualquier tipo de documento
    public void ProcesarTodos()
    {
        Console.WriteLine("\n=== Procesando todos los documentos ===");
        
        foreach (Documento doc in documentos)
        {
            Console.WriteLine($"\n--- Procesando: {doc.Titulo} ---");
            doc.Abrir();        // Polimorfismo en acción
            doc.Procesar();     // Polimorfismo en acción
            doc.MostrarInformacion(); // Polimorfismo en acción
            doc.Guardar();      // Polimorfismo en acción
        }
    }
    
    public void ProcesarPorTipo<T>() where T : Documento
    {
        Console.WriteLine($"\n=== Procesando documentos de tipo {typeof(T).Name} ===");
        
        foreach (Documento doc in documentos)
        {
            if (doc is T documentoTipo)
            {
                Console.WriteLine($"\nProcesando: {documentoTipo.Titulo}");
                documentoTipo.Procesar();
            }
        }
    }
    
    public void MostrarEstadisticas()
    {
        Console.WriteLine("\n=== Estadísticas de documentos ===");
        Console.WriteLine($"Total de documentos: {documentos.Count}");
        
        var grupos = documentos.GroupBy(d => d.GetType().Name);
        foreach (var grupo in grupos)
        {
            Console.WriteLine($"{grupo.Key}: {grupo.Count()} documento(s)");
        }
    }
}

Ejemplo de uso completo

class Program
{
    static void Main()
    {
        var procesador = new ProcesadorDocumentos();
        
        // Agregar diferentes tipos de documentos
        procesador.AgregarDocumento(new DocumentoTexto("Manual de C#", "Ana García"));
        procesador.AgregarDocumento(new DocumentoImagen("Diagrama de Clases", "Luis Martín", 1920, 1080));
        procesador.AgregarDocumento(new DocumentoPDF("Especificaciones", "Carmen López", 25) 
        { 
            TieneSeguridad = true 
        });
        procesador.AgregarDocumento(new DocumentoTexto("Notas de Reunión", "Pedro Sánchez"));
        
        // Procesar todos polimórficamente
        procesador.ProcesarTodos();
        
        // Procesar por tipo específico
        procesador.ProcesarPorTipo<DocumentoPDF>();
        
        // Mostrar estadísticas
        procesador.MostrarEstadisticas();
        
        // Demostrar polimorfismo directo
        Console.WriteLine("\n=== Demostración directa de polimorfismo ===");
        Documento[] docs = {
            new DocumentoTexto("Ejemplo 1", "Autor 1"),
            new DocumentoImagen("Ejemplo 2", "Autor 2", 800, 600),
            new DocumentoPDF("Ejemplo 3", "Autor 3", 10)
        };
        
        foreach (Documento doc in docs)
        {
            // El mismo código funciona para todos los tipos
            doc.Abrir();
            doc.Procesar();
            Console.WriteLine("---");
        }
    }
}

Buenas prácticas del polimorfismo

Diseño de jerarquías polimórficas

// Jerarquía bien diseñada para vehículos
public abstract class Vehiculo
{
    public string Marca { get; set; }
    public string Modelo { get; set; }
    public int Año { get; set; }
    
    // Método virtual con implementación base sensata
    public virtual void Arrancar()
    {
        Console.WriteLine($"Arrancando {Marca} {Modelo}...");
    }
    
    // Método abstracto que fuerza implementación específica
    public abstract void Acelerar();
    
    // Método virtual que puede ser personalizado
    public virtual void Frenar()
    {
        Console.WriteLine("Aplicando frenos estándar");
    }
    
    // Método concreto que no debería cambiar
    public void MostrarInformacion()
    {
        Console.WriteLine($"Vehículo: {Marca} {Modelo} ({Año})");
    }
}

public class Coche : Vehiculo
{
    public override void Acelerar()
    {
        Console.WriteLine("Acelerando suavemente en carretera");
    }
    
    public override void Arrancar()
    {
        Console.WriteLine("Girando la llave de contacto...");
        base.Arrancar(); // Aprovecha la implementación base
    }
}

public class Motocicleta : Vehiculo
{
    public override void Acelerar()
    {
        Console.WriteLine("Acelerando con mayor agilidad");
    }
    
    public override void Arrancar()
    {
        Console.WriteLine("Presionando botón de arranque...");
        base.Arrancar();
    }
    
    public override void Frenar()
    {
        Console.WriteLine("Aplicando frenos delantero y trasero coordinadamente");
    }
}

Resumen

El polimorfismo y los métodos virtuales constituyen herramientas fundamentales para crear aplicaciones flexibles y extensibles en C#. Hemos explorado cómo los métodos virtuales permiten que las clases derivadas personalicen el comportamiento heredado, mientras que el polimorfismo facilita el tratamiento uniforme de objetos de diferentes tipos a través de una interfaz común.

Los conceptos clave incluyen la declaración de métodos virtuales con virtual, su sobrescritura con override, y el uso de sealed para controlar la cadena de herencia. El polimorfismo en tiempo de ejecución permite que el mismo código funcione con múltiples tipos de objetos, determinando el método específico a ejecutar según el tipo real del objeto. Estas características son esenciales para implementar patrones de diseño avanzados y crear sistemas que puedan evolucionar sin requerir cambios extensivos en el código existente.