Polimorfismo: Compilación vs. Ejecución
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 25 de 70.
El polimorfismo significa "muchas formas": la capacidad de que el mismo código se comporte de manera diferente según el contexto. C# admite dos tipos distintos de polimorfismo, y comprender cuándo se resuelve cada uno ayuda a escribir código más flexible.
El polimorfismo en tiempo de compilación (también llamado polimorfismo estático) es resuelto por el compilador antes de que se ejecute su programa. La sobrecarga de métodos es el ejemplo principal: múltiples métodos comparten el mismo nombre pero tienen diferentes parámetros:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // El compilador elige la versión int
calc.Add(5.0, 3.0); // El compilador elige la versión doubleEl compilador examina los tipos de argumentos y selecciona el método correcto en tiempo de compilación. Esta decisión queda fijada antes de que el programa se ejecute.
Polimorfismo en tiempo de ejecución (polimorfismo dinámico) se resuelve mientras el programa se ejecuta. Esto sucede con los métodos virtual y override: el tipo real del objeto determina qué método se ejecuta:
Animal pet = new Dog(); // Declarado como Animal, en realidad un Dog
pet.Speak(); // Llama al Speak() de Dog en tiempo de ejecuciónEl compilador no sabe qué Speak() se ejecutará; esa decisión ocurre en tiempo de ejecución basándose en el tipo real del objeto. Esto es lo que hace que el polimorfismo basado en herencia sea tan potente para construir sistemas flexibles y extensibles.
Desafío
FácilVamos a construir un sistema convertidor que demuestre ambos tipos de polimorfismo en acción. Crearás una clase que utiliza la sobrecarga de métodos (polimorfismo en tiempo de compilación) junto con una jerarquía de herencia que utiliza métodos virtual/override (polimorfismo en tiempo de ejecución).
Organizarás tu código en cuatro archivos:
Converter.cs: Define una claseConverteren el espacio de nombresConversion. Esta clase demuestra el polimorfismo en tiempo de compilación a través de la sobrecarga de métodos. Crea tres métodosConvertsobrecargados:Convert(int value)devuelve"Integer: {value}"Convert(double value)devuelve"Double: {value}"Convert(string value)devuelve"String: {value}"
Shape.cs: Define una clase baseShapeen el espacio de nombresConversion. Esta clase tiene una propiedadName(string) y un constructor que la establece. Incluye un métodovirtualllamadoDescribe()que devuelve"This is a {Name}".Circle.cs: Define una claseCircleen el espacio de nombresConversionque hereda deShape. Añade una propiedadRadius(double). El constructor acepta un radio y pasa"Circle"al constructor base. SobrescribeDescribe()para que devuelva"This is a Circle with radius {Radius}".Program.cs: En tu archivo principal, demuestra ambos tipos de polimorfismo. Primero, crea unConvertery llama a los tres métodos sobrecargados con valores de entrada. Luego crea unCircle, almacénalo en una variable de tipoShapey llama aDescribe()para mostrar el polimorfismo en tiempo de ejecución.
Recibirás cuatro entradas:
- Un valor entero
- Un valor double
- Un valor de cadena (string)
- Un radio de círculo (double)
Imprime la salida en este formato:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}Por ejemplo, si las entradas son 42, 3.14, Hello y 5.5, la salida debería ser:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5Observa la diferencia clave: el compilador decide qué método Convert llamar basándose en los tipos de argumentos en tiempo de compilación, mientras que el método Describe() se resuelve en tiempo de ejecución basándose en el tipo de objeto real, ¡aunque esté almacenado en una variable Shape!
Hoja de referencia
El polimorfismo significa "muchas formas": la capacidad de que el mismo código se comporte de manera diferente según el contexto.
El polimorfismo en tiempo de compilación (polimorfismo estático) es resuelto por el compilador antes de que se ejecute el programa. La sobrecarga de métodos es el ejemplo principal:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // El compilador elige la versión int
calc.Add(5.0, 3.0); // El compilador elige la versión doubleEl compilador examina los tipos de argumentos y selecciona el método correcto en tiempo de compilación.
El polimorfismo en tiempo de ejecución (polimorfismo dinámico) se resuelve mientras el programa se ejecuta utilizando los métodos virtual y override:
Animal pet = new Dog(); // Declarado como Animal, en realidad es un Dog
pet.Speak(); // Llama al Speak() de Dog en tiempo de ejecuciónEl tipo real del objeto determina qué método se ejecuta en tiempo de ejecución, no el tipo de variable declarado.
Pruébalo tú mismo
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// Leer entradas
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: Demonstrate Compile-time Polymorphism
// Crear un objeto Converter y llamar a los tres métodos Convert sobrecargados
Console.WriteLine("Compile-time Polymorphism:");
// Llamar a Convert con intValue, doubleValue y stringValue
// TODO: Demonstrate Runtime Polymorphism
// Crear un objeto Circle y almacenarlo en una variable Shape
// Luego llamar a Describe() en la variable Shape
Console.WriteLine("Runtime Polymorphism:");
// Crear Circle y llamar a Describe()
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes