Clases abstractas
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 60 de 104.
Una clase abstracta es cualquier clase que contiene al menos una función virtual pura. Debido a que tiene una interfaz incompleta, no se pueden crear objetos de una clase abstracta directamente; existe únicamente para ser heredada.
class Shape {
public:
virtual double area() = 0; // Virtual pura - hace que Shape sea abstracta
virtual double perimeter() = 0; // Otra función virtual pura
void printInfo() { // Método regular - tiene implementación
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // Error: no se puede instanciar una clase abstractaLas clases abstractas pueden contener una mezcla de funciones virtuales puras (sin implementación), funciones virtuales regulares (con implementación por defecto) y funciones no virtuales. Esto le permite definir un comportamiento común mientras requiere que las clases derivadas implementen una funcionalidad específica.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: Circle implementa todas las funciones virtuales puras
c.printInfo(); // Usa printInfo() heredado, llama a area() de CircleUna clase derivada debe implementar todas las funciones virtuales puras para volverse concreta (instanciable). Si deja alguna sin implementar, seguirá siendo abstracta. Este mecanismo asegura que cada clase concreta en su jerarquía proporcione la funcionalidad requerida.
Desafío
FácilVamos a construir un sistema de reproductor de medios que demuestre cómo las clases abstractas definen contratos para diferentes tipos de medios. Crearás una clase base abstracta que establezca lo que cada reproductor de medios debe hacer, y luego implementarás reproductores concretos para contenido de audio y video.
Organizarás tu código en tres archivos:
MediaPlayer.h: Define una clase abstractaMediaPlayerque sirva como plano para todos los tipos de medios. Tu clase abstracta debe tener:- Un miembro protegido
std::string title - Un miembro protegido
int duration(en segundos) - Un constructor que inicialice ambos valores
- Un método virtual puro
play()— cada tipo de medio se reproducirá de manera diferente - Un método virtual puro
getMediaType()que devuelva unstd::string - Un método regular (no virtual)
showInfo()que imprima:<title> (<duration>s) - Type: <mediaType>— este método debe llamar agetMediaType()para obtener el tipo - Un destructor virtual
- Un miembro protegido
Players.h: Implementa dos reproductores de medios concretos que hereden deMediaPlayer:AudioPlayer:- Un miembro privado
std::string artist - Un constructor que reciba title, duration y artist
- Implementa
play()para imprimir:Playing audio: <title> by <artist> - Implementa
getMediaType()para devolver"Audio"
VideoPlayer:- Un miembro privado
std::string resolution - Un constructor que reciba title, duration y resolution
- Implementa
play()para imprimir:Playing video: <title> in <resolution> - Implementa
getMediaType()para devolver"Video"
- Un miembro privado
main.cpp: Lee cuatro entradas (cada una en una línea separada):- Título de la pista de audio
- Duración del audio (entero)
- Título del video
- Duración del video (entero)
Crea un
AudioPlayercon el artista"Unknown Artist"y unVideoPlayercon la resolución"1080p". Almacena ambos en un arreglo de punterosMediaPlayer*.Recorre el arreglo y para cada reproductor, primero llama a
showInfo(), luego llama aplay(). Imprime una línea en blanco entre cada elemento multimedia. Limpia tus objetos asignados dinámicamente cuando hayas terminado.
Por ejemplo, con las entradas Summer Vibes, 180, Nature Documentary, y 3600:
Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist
Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080pObserva cómo el método showInfo() se define una vez en la clase base abstracta pero llama al método virtual puro getMediaType() — esto demuestra cómo las clases abstractas pueden mezclar implementaciones concretas con métodos que las clases derivadas deben proporcionar. Recuerda usar la palabra clave override en todos los métodos sobrescritos.
Hoja de referencia
Una clase abstracta contiene al menos una función virtual pura y no puede ser instanciada directamente. Existe para ser heredada y definir una interfaz para las clases derivadas.
Definición de una clase abstracta
Use = 0 para declarar una función virtual pura:
class Shape {
public:
virtual double area() = 0; // Función virtual pura
virtual double perimeter() = 0; // Hace que Shape sea abstracta
void printInfo() { // Método regular con implementación
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default; // Destructor virtual
};Las clases abstractas pueden contener:
- Funciones virtuales puras (sin implementación)
- Funciones virtuales regulares (con implementación por defecto)
- Funciones no virtuales
Implementación de clases concretas
Una clase derivada debe implementar todas las funciones virtuales puras para poder ser instanciada:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // Correcto: todas las funciones virtuales puras están implementadas
c.printInfo(); // El método heredado llama a area() de CircleSi una clase derivada no implementa todas las funciones virtuales puras, sigue siendo abstracta y no puede ser instanciada.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// Leer entradas
std::string audioTitle;
int audioDuration;
std::string videoTitle;
int videoDuration;
std::getline(std::cin, audioTitle);
std::cin >> audioDuration;
std::cin.ignore();
std::getline(std::cin, videoTitle);
std::cin >> videoDuration;
// TODO: Crear un AudioPlayer con el artista "Unknown Artist"
// TODO: Crear un VideoPlayer con la resolución "1080p"
// TODO: Almacenar ambos en un arreglo de punteros MediaPlayer*
// TODO: Recorrer el arreglo y para cada reproductor:
// 1. Llamar a showInfo()
// 2. Llamar a play()
// 3. Imprimir una línea en blanco entre cada elemento multimedia
// TODO: Limpiar los objetos asignados dinámicamente
return 0;
}
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 externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico