Sobrescritura de métodos
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 51 de 104.
La sobrescritura de métodos ocurre cuando una clase derivada proporciona su propia implementación de un método que ya existe en la clase base. Esto permite que las clases derivadas personalicen el comportamiento heredado mientras conservan la misma firma del método.
Para sobrescribir un método, simplemente define un método en la clase derivada con exactamente el mismo nombre, tipo de retorno y parámetros:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Salida: Woof!La versión de la clase derivada oculta la versión de la clase base. Sin embargo, todavía puedes acceder al método de la clase base usando el operador de resolución de ámbito:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Llama a la versión de la clase base
std::cout << "Woof!" << std::endl;
}
};Existe una limitación importante en esta forma básica de sobrescritura. Cuando accedes a un objeto derivado mediante un puntero o una referencia a la clase base, se llama a la versión de la clase base en su lugar:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Salida: Algún sonido (no Woof!)Esto ocurre porque el compilador determina qué método llamar según el tipo del puntero, no el tipo real del objeto. Para lograr un verdadero polimorfismo en tiempo de ejecución, en el que se llame al método correcto según el objeto real, necesitarás funciones virtuales, que veremos en la siguiente lección.
Desafío
FácilVamos a crear un sistema de notificaciones que demuestra cómo funciona la sobrescritura de métodos. Crearás una clase base Notifier y dos notificadores especializados que personalizan la forma de entregar los mensajes.
Organizarás el código en tres archivos:
Notifier.h: Define una clase baseNotifiercon:- Un miembro protegido
std::string recipient - Un constructor que recibe un nombre de destinatario y lo almacena
- Un método público
send(const std::string& message)que imprime:Sending to <recipient>: <message>
- Un miembro protegido
EmailNotifier.h: Define una claseEmailNotifierque hereda públicamente deNotifier:- Un constructor que recibe un destinatario y se lo pasa a la clase base
- Sobrescribe el método
send()para llamar primero a la versión de la clase base medianteNotifier::send()y después imprimir:[Email sent successfully]
main.cpp: Lee dos entradas (cada una en una línea distinta):- Nombre del destinatario (cadena)
- Contenido del mensaje (cadena)
Define una clase
SMSNotifierdirectamente en main.cpp que hereda públicamente deNotifier:- Un constructor que recibe un destinatario y se lo pasa a la clase base
- Sobrescribe el método
send()para imprimir:SMS to <recipient>: <message>(reemplazando por completo el comportamiento de la clase base sin llamarlo)
Crea un objeto de cada tipo de notificador (la clase base
Notifier,EmailNotifierySMSNotifier) usando el mismo destinatario. Llama asend()en cada uno con el mensaje de entrada e imprime una línea separadora entre cada llamada:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Por ejemplo, con las entradas Alice y Hello there!:
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!Observa cómo EmailNotifier amplía el comportamiento de la clase base al llamar primero al método de la clase padre, mientras que SMSNotifier lo reemplaza por completo con su propia implementación. No olvides las protecciones de encabezado e incluir los encabezados adecuados en cada archivo.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Define aquí la clase SMSNotifier que hereda públicamente de Notifier
// - Constructor que pasa recipient a la clase base
// - Sobrescribe send() para imprimir: SMS to <recipient>: <message>
// (reemplaza por completo el comportamiento de la clase base; no llames a send() de la clase padre)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Crea un objeto Notifier con recipient
// Imprime "--- Base Notifier ---"
// Llama a send() con message
// TODO: Crea un objeto EmailNotifier con recipient
// Imprime "--- Email Notifier ---"
// Llama a send() con message
// TODO: Crea un objeto SMSNotifier con recipient
// Imprime "--- SMS Notifier ---"
// Llama a send() con message
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