CSharp

Tutorial CSharp: Clases abstractas

Aprende el uso de clases y métodos abstractos en C# para diseñar jerarquías de clases con implementación parcial y contratos obligatorios.

Aprende CSharp y certifícate

Propósito y definición

Las clases abstractas representan un concepto fundamental en la programación orientada a objetos en C#, actuando como un puente entre las interfaces y las clases concretas. Una clase abstracta es aquella que no puede ser instanciada directamente, es decir, no podemos crear objetos a partir de ella utilizando el operador new. Su propósito principal es servir como base para otras clases que la extenderán.

A diferencia de las clases normales, las clases abstractas están diseñadas para ser incompletas por naturaleza. Esto significa que contienen una mezcla de implementación concreta (métodos con código) y definiciones abstractas (métodos sin implementación que las clases derivadas deben completar). Esta característica las hace ideales cuando queremos:

  • Definir una estructura común para un grupo de clases relacionadas
  • Proporcionar una implementación parcial que las clases hijas puedan aprovechar
  • Establecer un contrato que las clases derivadas deben cumplir

Para declarar una clase como abstracta en C#, utilizamos la palabra clave abstract antes de la palabra class:

public abstract class Figura
{
    // Propiedades y métodos
}

Una clase abstracta puede contener:

  • Campos y propiedades normales
  • Métodos con implementación completa
  • Métodos abstractos (sin implementación)
  • Constructores (aunque no se pueden usar para crear instancias directamente)

Veamos un ejemplo sencillo que ilustra el propósito de una clase abstracta:

public abstract class Animal
{
    // Propiedades normales
    public string Nombre { get; set; }
    
    // Constructor
    public Animal(string nombre)
    {
        Nombre = nombre;
    }
    
    // Método con implementación
    public void Respirar()
    {
        Console.WriteLine($"{Nombre} está respirando");
    }
    
    // Método abstracto (sin implementación)
    public abstract void HacerSonido();
}

En este ejemplo, Animal es una clase abstracta que:

  1. Define una propiedad común (Nombre) que todos los animales tendrán
  2. Proporciona un constructor para inicializar esa propiedad
  3. Implementa un comportamiento común (Respirar) que todos los animales comparten
  4. Declara un método abstracto (HacerSonido) que cada tipo específico de animal deberá implementar a su manera

Para crear una clase derivada de esta clase abstracta, usamos la herencia normal y debemos implementar todos los métodos abstractos:

public class Perro : Animal
{
    public Perro(string nombre) : base(nombre)
    {
    }
    
    // Implementación obligatoria del método abstracto
    public override void HacerSonido()
    {
        Console.WriteLine($"{Nombre} dice: ¡Guau guau!");
    }
}

Las clases abstractas son especialmente útiles en situaciones donde:

  • Tenemos una jerarquía de clases con comportamientos comunes
  • Queremos evitar la duplicación de código entre clases similares
  • Necesitamos forzar a las clases derivadas a implementar ciertos métodos
  • Deseamos proporcionar una implementación predeterminada que las clases hijas puedan usar o sobrescribir

A diferencia de las interfaces, que solo pueden definir "qué" debe hacer una clase, las clases abstractas pueden definir tanto el "qué" como el "cómo", proporcionando implementaciones parciales que las clases derivadas pueden aprovechar.

Es importante destacar que aunque no podemos crear instancias directas de una clase abstracta, sí podemos:

  • Declarar variables del tipo de la clase abstracta
  • Asignar a esas variables instancias de clases derivadas
  • Usar polimorfismo para trabajar con diferentes implementaciones
// Esto NO es posible:
// Animal miAnimal = new Animal("criatura"); // Error de compilación

// Esto SÍ es posible:
Animal miMascota = new Perro("Bobby");
miMascota.Respirar();      // Usa el método implementado en la clase base
miMascota.HacerSonido();   // Usa el método implementado en la clase derivada

Las clases abstractas representan un mecanismo poderoso para modelar conceptos del mundo real que comparten características pero que no pueden existir como entidades concretas por sí mismas. Por ejemplo, podemos tener una clase abstracta Vehículo que define propiedades comunes, pero siempre trabajaremos con instancias específicas como Coche, Motocicleta o Camión.

Métodos abstractos

Los métodos abstractos son una característica fundamental de las clases abstractas en C#. Estos métodos se declaran sin implementación (sin cuerpo) en la clase abstracta, lo que significa que no contienen código que defina su comportamiento. En su lugar, establecen un contrato que obliga a las clases derivadas a proporcionar una implementación específica.

Para declarar un método abstracto, utilizamos la palabra clave abstract y terminamos la declaración con punto y coma en lugar de un bloque de código:

public abstract void DibujarForma();

Algunas características importantes de los métodos abstractos:

  • Solo pueden existir dentro de clases abstractas
  • No tienen implementación en la clase base
  • Las clases derivadas deben implementarlos usando la palabra clave override
  • No pueden ser private (ya que necesitan ser accesibles para sobrescribirlos)
  • Pueden tener parámetros y tipos de retorno

Veamos un ejemplo práctico con una clase abstracta Instrumento que contiene un método abstracto:

public abstract class Instrumento
{
    public string Nombre { get; set; }
    
    // Método normal con implementación
    public void Afinar()
    {
        Console.WriteLine($"Afinando el {Nombre}");
    }
    
    // Método abstracto - sin implementación
    public abstract void Tocar();
}

Ahora, cualquier clase que herede de Instrumento debe proporcionar una implementación para el método Tocar():

public class Guitarra : Instrumento
{
    public Guitarra()
    {
        Nombre = "Guitarra";
    }
    
    // Implementación obligatoria del método abstracto
    public override void Tocar()
    {
        Console.WriteLine("Rasgando las cuerdas de la guitarra");
    }
}

public class Piano : Instrumento
{
    public Piano()
    {
        Nombre = "Piano";
    }
    
    // Implementación obligatoria del método abstracto
    public override void Tocar()
    {
        Console.WriteLine("Presionando las teclas del piano");
    }
}

Si intentamos crear una clase derivada sin implementar el método abstracto, el compilador generará un error:

// Esto causará un error de compilación
public class Violin : Instrumento
{
    // Error: 'Violin' no implementa el miembro abstracto heredado 'Instrumento.Tocar()'
}

Métodos abstractos con parámetros y valores de retorno

Los métodos abstractos pueden tener parámetros y devolver valores, igual que los métodos normales:

public abstract class Calculadora
{
    // Método abstracto con parámetros y valor de retorno
    public abstract double Calcular(double a, double b);
    
    // Método normal que utiliza el método abstracto
    public void MostrarResultado(double a, double b)
    {
        double resultado = Calcular(a, b);
        Console.WriteLine($"El resultado es: {resultado}");
    }
}

public class Sumadora : Calculadora
{
    // Implementación del método abstracto para sumar
    public override double Calcular(double a, double b)
    {
        return a + b;
    }
}

public class Multiplicadora : Calculadora
{
    // Implementación del método abstracto para multiplicar
    public override double Calcular(double a, double b)
    {
        return a * b;
    }
}

Uso práctico de métodos abstractos

Los métodos abstractos son especialmente útiles cuando queremos definir un comportamiento común pero la implementación específica varía según el tipo de objeto. Veamos un ejemplo más completo con una jerarquía de formas geométricas:

public abstract class Forma
{
    // Propiedades comunes
    public string Color { get; set; }
    
    // Constructor
    public Forma(string color)
    {
        Color = color;
    }
    
    // Métodos abstractos
    public abstract double CalcularArea();
    public abstract double CalcularPerimetro();
    
    // Método normal que usa métodos abstractos
    public void MostrarInformacion()
    {
        Console.WriteLine($"Forma de color {Color}");
        Console.WriteLine($"Área: {CalcularArea()}");
        Console.WriteLine($"Perímetro: {CalcularPerimetro()}");
    }
}

Ahora podemos crear clases concretas para diferentes formas:

public class Circulo : Forma
{
    public double Radio { get; set; }
    
    public Circulo(string color, double radio) : base(color)
    {
        Radio = radio;
    }
    
    public override double CalcularArea()
    {
        return Math.PI * Radio * Radio;
    }
    
    public override double CalcularPerimetro()
    {
        return 2 * Math.PI * Radio;
    }
}

public class Rectangulo : Forma
{
    public double Ancho { get; set; }
    public double Alto { get; set; }
    
    public Rectangulo(string color, double ancho, double alto) : base(color)
    {
        Ancho = ancho;
        Alto = alto;
    }
    
    public override double CalcularArea()
    {
        return Ancho * Alto;
    }
    
    public override double CalcularPerimetro()
    {
        return 2 * (Ancho + Alto);
    }
}

Y podemos usar estas clases de la siguiente manera:

// Creamos diferentes formas
Forma circulo = new Circulo("Rojo", 5);
Forma rectangulo = new Rectangulo("Azul", 4, 6);

// Llamamos al mismo método en diferentes objetos
circulo.MostrarInformacion();
rectangulo.MostrarInformacion();

Combinación de métodos abstractos y no abstractos

Una de las ventajas de las clases abstractas es que podemos combinar métodos abstractos (sin implementación) con métodos normales (con implementación). Esto nos permite:

  1. Definir comportamientos comunes en la clase base
  2. Forzar comportamientos específicos en las clases derivadas
public abstract class Empleado
{
    public string Nombre { get; set; }
    public int Antiguedad { get; set; }
    
    // Método abstracto - cada tipo de empleado calcula su salario de forma diferente
    public abstract decimal CalcularSalario();
    
    // Método normal - comportamiento común para todos los empleados
    public decimal CalcularBonusAnual()
    {
        // Bonus común basado en la antigüedad
        return Antiguedad * 100;
    }
    
    // Método que combina comportamiento común y específico
    public void ImprimirRecibo()
    {
        decimal salario = CalcularSalario(); // Llama al método abstracto
        decimal bonus = CalcularBonusAnual(); // Llama al método normal
        
        Console.WriteLine($"Empleado: {Nombre}");
        Console.WriteLine($"Salario base: {salario}");
        Console.WriteLine($"Bonus anual: {bonus}");
        Console.WriteLine($"Total: {salario + bonus}");
    }
}

public class Desarrollador : Empleado
{
    public decimal SalarioPorHora { get; set; }
    public int HorasTrabajadas { get; set; }
    
    public override decimal CalcularSalario()
    {
        return SalarioPorHora * HorasTrabajadas;
    }
}

public class Gerente : Empleado
{
    public decimal SalarioBase { get; set; }
    public decimal Comision { get; set; }
    
    public override decimal CalcularSalario()
    {
        return SalarioBase + Comision;
    }
}

Cuándo usar métodos abstractos

Los métodos abstractos son más adecuados cuando:

  • El comportamiento varía significativamente entre las subclases
  • No existe una implementación predeterminada razonable
  • Queremos forzar a las clases derivadas a proporcionar su propia implementación
  • El comportamiento depende de propiedades específicas de cada subclase

En resumen, los métodos abstractos nos permiten definir una estructura común para un grupo de clases relacionadas, mientras dejamos los detalles específicos de implementación a cada clase derivada. Esto facilita la creación de jerarquías de clases coherentes y bien organizadas, donde cada clase puede personalizar ciertos comportamientos mientras aprovecha la funcionalidad común proporcionada por la clase base.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende CSharp online

Ejercicios de esta lección Clases abstractas

Evalúa tus conocimientos de esta lección Clases abstractas con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de CSharp

Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A C#

Introducción Y Entorno

Creación De Proyecto C#

Introducción Y Entorno

Variables Y Constantes

Sintaxis

Tipos De Datos

Sintaxis

Operadores

Sintaxis

Control De Flujo

Sintaxis

Funciones

Sintaxis

Estructuras De Control Iterativo

Sintaxis

Interpolación De Strings

Sintaxis

Estructuras De Control Condicional

Sintaxis

Manejo De Valores Nulos

Sintaxis

Clases Y Encapsulación

Programación Orientada A Objetos

Objetos

Programación Orientada A Objetos

Constructores Y Destructores

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

Genéricos

Programación Orientada A Objetos

Métodos Virtuales Y Sobrecarga

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Propiedades Y Encapsulación

Programación Orientada A Objetos

Métodos De Extensión

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Clases Parciales

Programación Orientada A Objetos

Miembros Estáticos

Programación Orientada A Objetos

Tuplas Y Tipos Anónimos

Programación Orientada A Objetos

Arrays Y Listas

Colecciones Y Linq

Diccionarios

Colecciones Y Linq

Conjuntos, Colas Y Pilas

Colecciones Y Linq

Uso De Consultas Linq

Colecciones Y Linq

Linq Avanzado

Colecciones Y Linq

Colas Y Pilas

Colecciones Y Linq

Conjuntos

Colecciones Y Linq

Linq Básico

Colecciones Y Linq

Delegados Funcionales

Programación Funcional

Records

Programación Funcional

Expresiones Lambda

Programación Funcional

Linq Funcional

Programación Funcional

Fundamentos De La Programación Funcional

Programación Funcional

Pattern Matching

Programación Funcional

Testing Unitario Con Xunit

Testing

Excepciones

Excepciones

Delegados

Programación Asíncrona

Eventos

Programación Asíncrona

Lambdas

Programación Asíncrona

Uso De Async Y Await

Programación Asíncrona

Tareas

Programación Asíncrona

Accede GRATIS a CSharp y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué es una clase abstracta y su propósito en la programación orientada a objetos.
  • Aprender a declarar y utilizar clases abstractas y métodos abstractos en C#.
  • Diferenciar entre métodos abstractos y métodos con implementación en clases abstractas.
  • Implementar clases derivadas que extienden clases abstractas y sobrescriben métodos abstractos.
  • Aplicar conceptos de polimorfismo y jerarquías de clases usando clases abstractas.