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. Como tiene una interfaz incompleta, no puedes crear objetos de una clase abstracta directamente: existe únicamente para que se herede de ella.
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 normal - tiene implementación
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // Error: no se puede crear una instancia de una clase abstractaLas clases abstractas pueden contener una combinación de funciones virtuales puras (sin implementación), funciones virtuales normales (con implementación predeterminada) y funciones no virtuales. Esto te permite definir un comportamiento común y, al mismo tiempo, exigir que las clases derivadas implementen funcionalidades específicas.
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 el método heredado printInfo() y llama al método area() de CircleUna clase derivada debe implementar todas las funciones virtuales puras para convertirse en concreta (instanciable). Si deja alguna sin implementar, seguirá siendo abstracta. Este mecanismo garantiza que todas las clases concretas de tu jerarquía proporcionen la funcionalidad requerida.
Desafío
FácilConstruyamos un sistema de reproducción multimedia que demuestre cómo las clases abstractas definen contratos para distintos tipos de medios. Crearás una clase base abstracta que establece lo que debe hacer cada reproductor multimedia y, después, implementarás reproductores concretos para contenido de audio y video.
Organizarás tu código en tres archivos:
MediaPlayer.h: Define una clase abstractaMediaPlayerque sirve como modelo 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 distinta - Un método virtual puro
getMediaType()que devuelva unstd::string - Un método normal (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 multimedia concretos que hereden deMediaPlayer:AudioPlayer:- Un miembro privado
std::string artist - Un constructor que reciba el título, la duración y el artista
- 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 el título, la duración y la resolución
- 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 aparte):- 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". Guarda ambos en un arreglo de punterosMediaPlayer*.Recorre el arreglo y, para cada reproductor, llama primero a
showInfo()y después aplay(). Imprime una línea en blanco entre cada elemento multimedia. Cuando termines, libera los objetos asignados dinámicamente.
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 sola vez en la clase base abstracta, pero llama al método virtual puro getMediaType(). Esto demuestra cómo las clases abstractas pueden combinar implementaciones concretas con métodos que las clases derivadas deben proporcionar. Recuerda usar la palabra clave override en todos los métodos sobrescritos.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// Lee las 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: Crea un AudioPlayer con el artista "Unknown Artist"
// TODO: Crea un VideoPlayer con resolución "1080p"
// TODO: Almacena ambos en un arreglo de punteros MediaPlayer*
// TODO: Recorre el arreglo y, para cada reproductor:
// 1. Llama a showInfo()
// 2. Llama a play()
// 3. Imprime una línea en blanco entre cada elemento multimedia
// TODO: Libera 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 la POO
Archivos externosCompilación y construcción en C++Archivos de cabecera y archivos fuenteEspacios de nombres y ámbitoIntroducción a la POO en C++Clases y objetosEl puntero 'this'Métodos (funciones miembro)Atributos (miembros de datos)Fundamentos de constructores y destructoresRepaso: calculadora sencilla4Propiedades de clase
Miembros de instancia vs. estáticosGetters y settersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases friendRepaso: gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de constructores y destructoresSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualRepaso: jerarquía de empleados2Gestión de memoria
Memoria de pila vs. heapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Repaso: gestor de arreglos dinámicos5Encapsulación
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultación de informaciónStruct vs. classClases anidadas e internasRepaso: sistema de registros de estudiantes8Polimorfismo
Polimorfismo en compilación y en tiempo de ejecuciónSobrecarga de funcionesRepaso de las funciones virtualesFunciones virtuales purasClases abstractasDiseño de interfaces en C++Conversión dinámica y RTTIRepaso: calculadora de formas11Conceptos avanzados de POO
Composición frente a herenciaMixins mediante CRTPIdiom PimplBorrado de tiposClases enum 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 predeterminadoConstructor con parámetrosConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis detallado del destructorRegla de tres / cinco / ceroRepaso: clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujoSobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tipoRepaso: clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasFundamentos de SFINAE y rasgos de tiposRepaso: contenedor genéricoPractica por tu cuenta: Compilador de C++ online