Está en la página 1de 23

Lenguajes de Programación II Página 123

UNIVERSIDAD CATÓLICA DE SANTA MARÍA


PROGRAMA PROFESIONAL DE INGENIERÍA DE SISTEMAS

SESIÓN N° 08:

Clases Abstractas, Interfaces, Funciones Virtuales,


Remplazo y refinamiento en C++

I
OBJETIVOS

❖ Identificar las características y propiedades de las interfaces en la programación orientada a


objetos.
❖ Reconocer la utilidad y el propósito de los métodos virtuales en la herencia y el polimorfismo.
❖ Comprender cómo el reemplazo y el refinamiento se aplican en la programación orientada a
objetos.
❖ Implementar interfaces en clases para establecer contratos y garantizar la implementación de
ciertos métodos.
❖ Desarrollar programas que utilicen clases abstractas, interfaces, métodos virtuales, reemplazo y
refinamiento para resolver problemas del mundo real.
❖ Valorar la importancia de la abstracción y la modularidad en el diseño de sistemas de software.
❖ Demostrar perseverancia y resiliencia al enfrentar desafíos en la implementación de soluciones
basadas en POO.

II
TEMAS A TRATAR

❖ Introducción.
❖ Clases abstractas
❖ ¿Qué es una clase Completamente Abstracta?
❖ ¿Qué es un método virtual?
❖ Añadir, Reemplazar y Refinar
❖ Resumen

III
MARCO TEORICO

1. INTRODUCCIÓN
En la programación orientada a objetos (POO), existen varios conceptos clave que nos
permiten diseñar sistemas más flexibles, modulares y fáciles de mantener. En este capítulo,
exploraremos cuatro de estos conceptos fundamentales: Clases Abstractas, Interfaces,
Funciones Virtuales y Remplazo/Refinamiento.
Comenzaremos por comprender qué son las Clases Abstractas y las Interfaces, y cómo se
utilizan para establecer una base común en la jerarquía de clases y definir contratos para
las clases derivadas. Luego, nos adentraremos en el mundo de las Funciones Virtuales y
cómo permiten el polimorfismo, brindando la capacidad de redefinir comportamientos en las
clases hijas. Por último, exploraremos los conceptos de Remplazo y Refinamiento, y cómo
nos ayudan a mantener la coherencia y flexibilidad en la evolución de nuestros sistemas.
A lo largo de este capítulo, analizaremos ejemplos prácticos y estudiaremos cómo estos
conceptos se aplican en diferentes lenguajes de programación orientados a objetos, como
C++, Java, Python y JavaScript. Veremos cómo cada uno de estos conceptos se utiliza para
resolver problemas del mundo real y cómo nos permiten escribir código más modular,
reutilizable y mantenible.

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 124

2. CLASES ABSTRACTAS
Una clase abstracta es la que no instancia objetos, para tal caso se debe evitar que
funcionalmente una clase así no pueda hacerlo, esto se consigue creando métodos virtuales
puros, para convertir a un método en un método virtual puro se le iguala a cero en la
declaración, esto hace que no pueda ser definida en la clase donde se declara,
El concepto de métodos virtuales está asociado al polimorfismo, las clases abstractas
también están asociadas a este concepto.

// Fig. 25.01: unaclaseabstracta.h


// Declaración de la clase abstracta, se declara método virtual puro.

#ifndef UNACLASEABSTRACTA_H
#define UNACLASEABSTRACTA_H

class UnaClaseAbstracta
{
public:
UnaClaseAbstracta(int,char,float);
void ingreso();
void salida();
// método virtual puro
virtual void metodoVirtualPuro() = 0;
int getAtributoEntero();
float getAtributoFloat();
char getAtributoChar();
private:
int atributoEntero;
char atributochar;
float atributoFloat;
};
#endif

En la implementación de los métodos de la clase no se define el método virtual puro de la


clase.

// Fig. 25.02: unaclaseabstracta.cpp


// Implementación de la clase abstracta, no se define el método virtual puro.

#include "unaclaseabstracta.h"
#include <iostream>
using namespace std;
UnaClaseAbstracta::UnaClaseAbstracta(int a, char b, float c)
{
atributoEntero = a;
atributochar = b;
atributoFloat = c;
}
int UnaClaseAbstracta::getAtributoEntero()
{
return atributoEntero;
}
float UnaClaseAbstracta::getAtributoFloat()
{
return atributoFloat;
}
char UnaClaseAbstracta::getAtributoChar()
{
return atributochar;
}
void UnaClaseAbstracta::ingreso()
{
cout << "Ingreso de objeto de una clase: " << endl;
cout << "atributoEntero = ";

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 125

cin >> atributoEntero;


cout << "atributochar = ";
cin >> atributochar;
cout << "atributoFloat = " ;
cin >> atributoFloat;
}
void UnaClaseAbstracta::salida()
{
cout << "Objeto de una clase: " << endl;
cout << "atributoEntero = " << atributoEntero << endl;
cout << "atributochar = " << atributochar << endl;
cout << "atributoFloat = " << atributoFloat << endl;
}

La clase UnaClaseAbstracta, abstracta ahora, las clases que deriven de ella deben
implementar el método virtual puro: virtual void metodoVirtualPuro() = 0.

// Fig. 25.03: unaclaseabstracta.h


// Declaración de la clase concreta, se define método virtual puro.
#ifndef UNACLASECONCRETA_H
#define UNACLASECONCRETA_H

#include "unaclaseabstracta.h"
class UnaClaseConcreta : public UnaClaseAbstracta
{
public:
UnaClaseConcreta(int = 0, char = ' ', float = 0);
void ingreso();
void salida();
// El método virtual puro debe ser definido aqui
void metodoVirtualPuro();
private:
int atributoEnteroDerivada;
char atributocharDerivada;
float atributoFloatDerivada;
};
#endif

En su respectivo archivo unaclaseabstracta.cpp se implementa el métdo virtual puro.

// Fig. 25.04: unaclaseconcreta.cpp


// Implementación de la clase concreta, se define el método virtual puro.
#include "unaclaseconcreta.h"
#include <iostream>
using namespace std;
UnaClaseConcreta::UnaClaseConcreta(int a, char b, float c) :
atributoEnteroDerivada(a), atributocharDerivada(b),
atributoFloatDerivada(c), UnaClaseAbstracta(a,b,c)
{
}
void UnaClaseConcreta::ingreso()
{
cout << "Ingreso de objeto de una clase derivada: " << endl;
cout << "atributoEnteroDerivada = ";
cin >> atributoEnteroDerivada;
cout << "atributocharDerivada = ";
cin >> atributocharDerivada;
cout << "atributoFloatDerivada = " ;
cin >> atributoFloatDerivada;
UnaClaseAbstracta::ingreso();
}
void UnaClaseConcreta::salida()
{
cout << "Objeto de una clase: " << endl;

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 126

cout << "atributoEnteroDerivada = " << atributoEnteroDerivada << endl;


cout << "atributocharDerivada = " << atributocharDerivada << endl;
cout << "atributoFloatDerivada = " << atributoFloatDerivada << endl;
UnaClaseAbstracta::salida();
}
// Definición del método virtual puro
void UnaClaseConcreta::metodoVirtualPuro()
{
cout << "operacion1: " << atributoEnteroDerivada + getAtributoEntero() <<
endl;
}

A. REGLAS DE USO:
Las clases abstractas se usan en el contexto siguiente:
▪ Una clase abstracta no puede ser utilizada como argumento o como retorno de una
función.
▪ Al no instanciar objetos tampoc puede ser usada como argumento ni valor de retorno
de un método o función.
▪ Se pueden declarr punteros ya que puede apuntar a objetos de clases derivadas.
▪ Tambié se pueden crear instancias si el objeto que inicializa la referencia no es
necesario.

// Fig. 25.05: ejecutar.cpp


// Demostración de la clase concreta y abstracta
#include <iostream>
using namespace std;
#include "unaclaseconcreta.h"
#include "unaclaseabstracta.h"
// Error: No se pueden recibir ni devolver objetos de clases abstractas
UnaClaseAbstracta funcion1(UnaClaseAbstracta);
// Se pueden recibir y devolver punteros del tipo de clase abstracta
UnaClaseAbstracta& funcion2(UnaClaseAbstracta&);

// Se pueden recibir y devolver referencias del tipo de clase abstracta


UnaClaseAbstracta* funcion3(UnaClaseAbstracta*);
int main()
{
// Error: No se pueden crear objetos de clases abstractas
UnaClaseAbstracta objUnaClaseAbstracta;

UnaClaseConcreta objUnaClaseConcreta(1,'a',3);
UnaClaseConcreta *ptrUnaClaseConcreta = new UnaClaseConcreta(2,'b',6);
// Se pueden crear punteros del tipo de clase abstracta
UnaClaseAbstracta *ptrObjUnaClaseAbstracta;

// Se pueden crear referencias del tipo de clase abstracta


UnaClaseAbstracta &refObjUnaClaseAbstracta = objUnaClaseConcreta;

objUnaClaseConcreta.metodoVirtualPuro();
ptrUnaClaseConcreta -> metodoVirtualPuro();

system("pause");
return 0;
}

Si una clase derivada de una clase abstracta no implementa todos los métodos virtuales
puros definidos en las clases de donde deriva esta también se vuelve abstracta.
Las funciones-miembro pueden ser llamadas desde el constructor de una clase abstracta,
pero la llamada directa o indirecta de una función virtual pura desde tal constructor puede
provocar un error en tiempo de ejecución. Sin embargo, son permitidas disposiciones como
la siguiente:

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 127

La razón es la ya señalada, de que la utilización del operador :: de acceso a ámbito anula


el mecanismo de funciones virtuales.

3. ¿QUÉ ES UNA CLASE COMPLETAMENTE ABSTRACTA?


Se entiende por clase completamente abstracta a una clase que solo contiene como
miembros a métodos virtuales puros, al usarse el mecanismo de separación de la definición
de clase ya la implementación de métodos d forma externa a la clase caeremos en la cuenta
que no se necesita de un archivo .cpp.

A. ¿CÓMO SE IMPLEMENTA UNA CLASE COMPLETAMENTE


ABSTRACTA?
Pasemos ahora a ver un ejemplo de uso de clases completamente abstractas en C++. Lo
primero que conviene recordar es que C++ no dispone de interfaces", y lo que vamos a
hacer es simular los mismos por medio de "clases completamente abstractas". Por lo
demás, el código del ejemplo quedaría como sigue:

// Fig. 26.01: unaclase.h


// Declaración de una clase cualquiera para herencia

#ifndef UNACLASE_H
#define UNACLASE_H
class UnaClase
{
public:
UnaClase(int,char,float);
void ingreso();
void salida();
int getAtributoEntero();
float getAtributoFloat();
char getAtributoChar();
private:
int atributoEntero;
char atributochar;
float atributoFloat;
};
#endif

La clase anterior será usada como clase base convencional, aquí no existen métodos
virtuales puros, por lo que dicha clase no es abstracta. Veamos ahora cómo queda el
archivo "unaclase.cpp":

// Fig. 26.02: unaclase.cpp


// Definición de los métodos de UnaClase
#include "unaclase.h"
#include <iostream>
using namespace std;
UnaClase::UnaClase(int a, char b, float c)
{
atributoEntero = a;
atributochar = b;
atributoFloat = c;
}
int UnaClase::getAtributoEntero()
{
return atributoEntero;
}
float UnaClase::getAtributoFloat()
{
return atributoFloat;
}

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 128

char UnaClase::getAtributoChar()
{
return atributochar;
}
void UnaClase::ingreso()
{
cout << "Ingreso de objeto de una clase: " << endl;
cout << "atributoEntero = ";
cin >> atributoEntero;
cout << "atributochar = ";
cin >> atributochar;
cout << "atributoFloat = " ;
cin >> atributoFloat;
}
void UnaClase::salida()
{
cout << "Objeto de una clase: " << endl;
cout << "atributoEntero = " << atributoEntero << endl;
cout << "atributochar = " << atributochar << endl;
cout << "atributoFloat = " << atributoFloat << endl;
}

No existe nada especial en el código.


Pasamos ahora a introducir la clase completamente abstracta UnaInterface, que se
corresponde a la interface que tendríamos en Java. Insistimos una vez más en que, al
contener la misma sólo declaraciones de métodos, en C++ sólo da lugar a un fichero de
cabeceras, de nombre UnaInterface.h, y por tanto no habrá fichero *.cpp. También
conviene destacar que todos los métodos en la misma serán declarados como abstractos,
lo cual implica que en C++ deberán llevar el añadido "= 0" en su declaración. Como
esperamos comportamiento polimorfo de los mismos, también llevarán el modificador
"virtual". Por defecto, en C++ los elementos de una clase que no llevan modificador de
visibilidad son de tipo private, así que deberemos añadir también el modificador public.
Con estas consideraciones, el fichero UnaInterface.h quedará como sigue:

// Fig. 26.03: unainterface.h


// Definición de una clase completamente abstracta
#ifndef UNAINTERFACE_H
#define UNAINTERFACE_H
class UnaInterface
{
public:
virtual void operacion1() = 0;
virtual void operacion2() = 0;
virtual void operacion3() = 0;
};
#endif

La clase completamente abstracta se definió en el código anterior, como se observa no


existen interfaces en Lenguaje C++ tal como existen en Lenguaje Java, por lo que el tipo
de relación que debe ser definida para que la clase pueda implantar la operatividad que
describe la clase completamente abstracta es basada en herencia. De este modo, la clase
UnaClaseDerivada ahora heredará de las clases UnaClase y UnaInterface,
implantándose así en base a herencia multiple:

// Fig. 26.04: unaclasederivada.h


// Definición de una clase derivada con herencia múltiple
#ifndef UNACLASEDERIVADA_H
#define UNACLASEDERIVADA_H
#include "unaclase.h"
#include "unainterface.h"
class UnaClaseDerivada : public UnaClase, public UnaInterface
{
public:

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 129

UnaClaseDerivada(int = 0, char = ' ', float = 0);


void ingreso();
void salida();
// Define los métodos virtuales puros de la interfaz
void operacion1();
void operacion2();
void operacion3();
private:
int atributoEnteroDerivada;
char atributocharDerivada;
float atributoFloatDerivada;
};
#endif

La herencia múltiple hace que la clase resultante reciba características de ambas clases, el
especificador de acceso era public, por lo cual se recibió la herencia de forma pública,
teniendo en cuenta que de la clase UnaInterface solo se reciben los métodos virtuales
puros que tendrán que ser implantados en esta clase o de lo contrario también se volverá
abstracta.

// Fig. 26.05: unaclasederivada.cpp


// Definición de una clase derivada con herencia múltiple

#include "unaclasederivada.h"
#include <iostream>
using namespace std;
UnaClaseDerivada::UnaClaseDerivada(int a, char b, float c) :
atributoEnteroDerivada(a),atributocharDerivada(b),
atributoFloatDerivada(c), UnaClase(a,b,c)
{
}
void UnaClaseDerivada::ingreso()
{
cout << "Ingreso de objeto de una clase derivada: " << endl;
cout << "atributoEnteroDerivada = ";
cin >> atributoEnteroDerivada;
cout << "atributocharDerivada = ";
cin >> atributocharDerivada;
cout << "atributoFloatDerivada = " ;
cin >> atributoFloatDerivada;
UnaClase::ingreso();
}
void UnaClaseDerivada::salida()
{
cout << "Objeto de una clase: " << endl;
cout << "atributoEnteroDerivada = " << atributoEnteroDerivada << endl;
cout << "atributocharDerivada = " << atributocharDerivada << endl;
cout << "atributoFloatDerivada = " << atributoFloatDerivada << endl;
UnaClase::salida();
}
// Definición de los métodos virtuales puros heredados
void UnaClaseDerivada::operacion1()
{
cout << "operacion1: " << atributoEnteroDerivada + getAtributoEntero()
<< endl;
}

void UnaClaseDerivada::operacion2()
{
cout << "operacion2: " << getAtributoFloat() + atributoFloatDerivada
<< endl;
}

void UnaClaseDerivada::operacion3()
{
cout << "operacion3: " << atributocharDerivada << getAtributoChar()

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 130

<< endl;
}

Con respecto a los métodos abstractos heredados de UnaInterface, únicamente señalar


que todos ellos deben ser definidos en UnaClaseDerivada para que la clase deje de ser
abstracta. Veamos ahora un sencillo ejemplo de programa principal programado sobre las
anteriores clases:

// Fig. 26.06: ejecutar.cpp


// Demostración de interfaces

#include <iostream>
using namespace std;

#include "unaclasederivada.h"
#include "unaclase.h"

int main()
{
UnaClaseDerivada *ptrUnaClaseDerivada = new UnaClaseDerivada(2,'b',6);
UnaClaseDerivada objUnaClaseDerivada(1,'a',3);

objUnaClaseDerivada.operacion1();
objUnaClaseDerivada.operacion2();
objUnaClaseDerivada.operacion3();
ptrUnaClaseDerivada -> operacion1();
ptrUnaClaseDerivada -> operacion2();
ptrUnaClaseDerivada -> operacion3();
system("pause");
return 0;
}

Es importante observar que esperamos que los métodos objeto creado


ptrUnaClaseDerivada se comporten de modo polimórfico y por lo tanto es necesario
declararlo como un puntero a objeto, en memoria dinámica.

4. ¿QUÉ ES UN MÉTODO VIRTUAL?


Un método virtual es un concepto de la programación orientada a objetos que permite que
un método en una clase base pueda ser redefinido en una clase derivada. Un método virtual
proporciona una implementación base en la clase base, pero permite que las clases
derivadas proporcionen su propia implementación personalizada.
Cuando se declara un método como virtual en la clase base, se indica que ese método
puede ser redefinido en las clases derivadas. Esto significa que, al llamar al método en un
objeto de la clase derivada, se ejecutará la implementación específica de esa clase derivada
en lugar de la implementación de la clase base.
La principal ventaja de los métodos virtuales es que permiten el polimorfismo, que es la
capacidad de tratar objetos de diferentes clases derivadas como objetos de la clase base.
Esto proporciona una flexibilidad adicional al permitir que diferentes objetos se comporten
de manera diferente según su clase derivada, pero aún se puedan manipular y utilizar de
manera generalizada a través de la clase base.
En muchos lenguajes de programación orientados a objetos, como C++, Java y Python, los
métodos virtuales se declaran utilizando palabras clave específicas. En C++, se utiliza la
palabra clave virtual, en Java se utiliza la anotación @Override, y en Python todos los
métodos son virtualmente polimórficos por defecto.

A. ¿CÓMO SE DECLARA UN MÉTODO VIRTUAL?


Al anteponer la palabra clave virtual a un método de clase estamos indicando que será
polimórfico y que será definido luego en una clase derivada. La cabecera del método no
tiene ninguna diferencia adicional con notro método. Ejemplo:

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 131

virtual void metodo();

Lo que se codifica en una clase de la siguiente forma:

// Fig. 27.01: clasebase.h


// Declaración de la clase base con métodos virtuales

#ifndef CLASEBASE_H
#define CLASEBASE_H

class ClaseBase
{
public:
ClaseBase(int i) : atributoEntero(i)
{
}
// Métodos virtuales
virtual void metodo1();
virtual int metodo2();
virtual int metodo3(int);
virtual void metodo4(int);
private:
int atributoEntero;
};
#endif

Cuando el método es externo se le coloca la palabra clave virtual en la declaración de clase,


pero en la definición externa ya no se coloca. Ejemplo:

// Fig. 27.02: clasebase.cpp


// Definición de la clase base con métodos virtuales

#include "clasebase.h"

#include <iostream>
using namespace std;

void ClaseBase::metodo1()
{
cout << "metodo vitual ClaseBase::metodo1(). . ." << endl;
}
int ClaseBase::metodo2()
{
cout << "metodo vitual ClaseBase::metodo2(). . ." << endl;
return 0;
}
int ClaseBase::metodo3(int a)
{
cout << "metodo vitual ClaseBase::metodo3(). . ." << endl;
return 0;
}
void ClaseBase::metodo4(int a)
{
cout << "metodo vitual ClaseBase::metodo4(). . ." << endl;
};

En las clases derivadas la implantación de los métodos virtuales se hará en función de la


propia semántica de la clase:
Cuando se declare una función como virtual tenga en mente que:
▪ Solo pueden ser métodos (funciones-miembro).
▪ No pueden ser declaradas friend de otras clases.

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 132

▪ No pueden ser miembros estáticos


▪ Los constructores no pueden ser declarados virtuales
▪ Los destructores sí pueden ser virtuales.

B. INVOCACIÓN DE FUNCIONES VIRTUALES


La sintaxis de invocación para métodos virtuales es la misma que para cualquier otro
método convencional no virtual, en realidad la diferencia se hace notar en la forma como el
compilador hace el enlace entre el objeto y el método virtual, ya que este proceso se lleva
cabo haciendo uso de la técnica de enlace dinámico o tardío:

// Fig. 27.03: ejecutar.cpp


// Demostración de métodos virtuales

#include <iostream>
using namespace std;

#include "clasebase.h"
#include "clasederivada.h"
int main()
{
ClaseBase objClaseBase(0);
ClaseBase* ptrClaseBase = new ClaseBase(5);
ClaseDerivada objptrClaseDerivada(3);
ClaseDerivada *ptrClaseDerivada = new ClaseDerivada(6);
objClaseBase.metodo1();
ptrClaseBase -> metodo1();
objptrClaseDerivada.metodo1();
objptrClaseDerivada.metodo2();
objptrClaseDerivada.ClaseBase::metodo1();
ptrClaseDerivada -> metodo1();
ptrClaseDerivada -> ClaseBase::metodo1();
system("pause");
return 0;
}

Se puede observar que el mismo método metodo1() es ejecutado por dos objetos diferentes
en las llamadas: objClaseBase.metodo1() y
objptrClaseDerivada.ClaseBase::metodo1(), en cambio, en
objptrClaseDerivada.metodo1() y objptrClaseDerivada.metodo2(), el mismo
objeto ejecuta métodos diferentes.

C. INVOCACIÓN EN JERARQUÍAS DE CLASES


Cuando una clase base tiene una función virtual y una clase derivada de forma pública de
ésta tiene la misma función implementada, al hacer una llamada al método mediante un
objeto de la clase derivada, esta invocación ejecutara al método virtual de la clase derivada
aun así se haga mediante punteros o referencias.

// Fig. 27.04: ejecutar.cpp


// Demostración de interfaces

#include <iostream>
using namespace std;

#include "clasebase.h"
#include "clasederivada.h"

int main()
{
ClaseBase *ptrClaseBase;
ClaseDerivada objClaseDerivada(3);
ClaseDerivada *ptrClaseDerivada = new ClaseDerivada(6);

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 133

ClaseBase &refClaseBase = objClaseDerivada;


ptrClaseBase = &objClaseDerivada;

objClaseDerivada.metodo1();
ptrClaseDerivada -> metodo1();
ptrClaseBase -> metodo1();
refClaseBase.metodo1();

system("pause");
return 0;
}

D. TABLA DE FUNCIONES VIRTUALES


Cuando se trata de métodos virtuales el compilador debe vincular la dirección del método
con el objeto invocado, pero esto no es posible ya que únicamente en tiempo de ejecución
sabremos quién en realidad invoca al método, para solucionar este problema se crea una
tabla llamada vtable, donde cada objeto de la clase derivada debe incluir un puntero a dicha
tabla de direcciones de métodos virtuales. Mediante dicho puntero el objeto puede
seleccionar en tiempo de ejecución el método virtual que tiene que invocar. Este mecanismo
manejado en tiempo de ejecución es menos eficiente que el mecanismo convencional de
ligadura estática o temprana.

E. TIPOS DEVUELTOS POR LAS FUNCIONES VIRTUALES


Al redefinirse un método virtual no se puede cambiarse el tipo de valor de retorno. Al hacer
dicho proceso la firma del método debe ser idéntica. Si existen diferencias, el compilador
C++ considera que se trata de funciones diferentes (un caso de sobrecarga) y se ignora el
mecanismo de métodos virtuales. Aun así, se puede cambiar el valor de retorno colocando
un puntero o referencia a clase derivada, cuando la versión en clase base lo hacía a un
puntero o referencia a clase base. Declaremos clases y apliquemos herencia:

// Fig. 27.05: otraclase.h


// Declaración de una clase base cualquiera para herencia

#ifndef OTRACLASEBASE_H
#define OTRACLASEBASE_H
class OtraClaseBase
{
public:
OtraClaseBase(int i) : atributoEntero(i)
{
}
private:
int atributoEntero;
};
#endif

Derivemos de la anterior:

// Fig. 27.06: OtraClaseDerivada.h


// Declaración de una clase base cualquiera para herencia

#ifndef OTRACLASEDERIVADA_H
#define OTRACLASEDERIVADA_H
#include "otraclasebase.h"
class OtraClaseDerivada : public OtraClaseBase
{
public:
OtraClaseDerivada(int a) : atributoEntero(a), OtraClaseBase(a)
{
}

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 134

private:
int atributoEntero;
};
#endif

Ahora crearemos dos clases en otra jerarquía aparte para probar lo anterior, estas tendrán
métodos virtuales.

// Fig. 27.07: otraclase.h


// Declaración de una clase base cualquiera para herencia

#ifndef CLASEBASE_H
#define CLASEBASE_H

#include "otraclasebase.h"
class ClaseBase
{
public:
ClaseBase(int i) : atributoEntero(i)
{
}
virtual void metodo1();
virtual int metodo2();
virtual int metodo3(int);
virtual void metodo4(int);
virtual OtraClaseBase* metodo5();
virtual OtraClaseBase& metodo6();
private:
int atributoEntero;
};
#endif

La definición de los métodos de clase se hará de esta forma:

// Fig. 27.08: otraclase.cpp


// Declaración de una clase base cualquiera para herencia

#include "clasebase.h"

#include <iostream>
using namespace std;

void ClaseBase::metodo1()
{
cout << "metodo vitual ClaseBase::metodo1(). . ." << endl;
}
int ClaseBase::metodo2()
{
cout << "metodo vitual ClaseBase::metodo2(). . ." << endl;
return 0;
}
int ClaseBase::metodo3(int a)
{
cout << "metodo vitual ClaseBase::metodo3(). . ." << endl;
return 0;
}
void ClaseBase::metodo4(int a)
{
cout << "metodo vitual ClaseBase::metodo4(). . ." << endl;
};
OtraClaseBase* ClaseBase::metodo5()
{
OtraClaseBase objOtraClaseBase(5);
OtraClaseBase* ptrOtraClaseBase = &objOtraClaseBase;

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 135

cout << "metodo vitual ClaseBase::metodo5(). . ." << endl;


return ptrOtraClaseBase;
}
OtraClaseBase& ClaseBase::metodo6()
{
OtraClaseBase objOtraClaseBase(2);
OtraClaseBase& refOtraClaseBase = objOtraClaseBase;
cout << "metodo vitual ClaseBase::metodo6(). . ." << endl;
return refOtraClaseBase;
}

La clase derivada tendrá una modificación en los valores de retorno de la referencia y el


puntero:

// Fig. 27.09: OtraClaseDerivada.h


// Declaración de una clase base cualquiera para herencia

#ifndef CLASEDERIVADA_H
#define CLASEDERIVADA_H
#include "clasebase.h"
#include "otraclasederivada.h"
class ClaseDerivada : public ClaseBase
{
public:
ClaseDerivada(int a) : atributoEntero(a), ClaseBase(a)
{
}
void metodo1();
int metodo2();
int metodo3(int);
void metodo4(int);
OtraClaseDerivada* metodo5();
OtraClaseDerivada& metodo6();
private:
int atributoEntero;
};
#endif

En la definición de los métodos de la clase derivada se está cambiando el tipo de referencia


y puntero que devuelven los métodos virtuales, se debe recordar que son de la misma
jerarquía:

// Fig. 27.10: otraclase.h


// Declaración de una clase base cualquiera para herencia

#include "clasederivada.h"

#include <iostream>
using namespace std;

void ClaseDerivada::metodo1()
{
cout << "metodo vitual ClaseDerivada::metodo1(). . ." << endl;
}
int ClaseDerivada::metodo2()
{
cout << "metodo vitual ClaseDerivada::metodo2(). . ." << endl;
return 0;
}
int ClaseDerivada::metodo3(int a)
{
cout << "metodo vitual ClaseDerivada::metodo3(). . ." << endl;
return 0;
}

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 136

void ClaseDerivada::metodo4(int a)
{
cout << "metodo vitual ClaseDerivada::metodo4(). . ." << endl;
};
OtraClaseDerivada* ClaseDerivada::metodo5()
{
OtraClaseDerivada objOtraClaseDerivada(5);
OtraClaseDerivada* ptrOtraClaseDerivada = &objOtraClaseDerivada;
cout << "metodo vitual ClaseDerivada::metodo5(). . ." << endl;
return ptrOtraClaseDerivada;
}
OtraClaseDerivada& ClaseDerivada::metodo6()
{
OtraClaseDerivada objOtraClaseDerivada(2);
OtraClaseDerivada& refOtraClaseDerivada = objOtraClaseDerivada;
cout << "metodo vitual ClaseDerivada::metodo6(). . ." << endl;
return refOtraClaseDerivada;
}

La implantación de las clases anteriores se hace en el siguiente programa:

// Fig. 27.11: ejecutar.cpp


// Demostración de interfaces

#include <iostream>
using namespace std;
#include "clasebase.h"
#include "clasederivada.h"
#include "otraclasebase.h"
#include "otraclasederivada.h"
int main()
{
ClaseBase objClaseBase(0);
ClaseBase* ptrClaseBase = new ClaseBase(5);
ClaseDerivada objptrClaseDerivada(3);
ClaseDerivada *ptrClaseDerivada = new ClaseDerivada(6);
objClaseBase.metodo1();
objClaseBase.metodo2();
objClaseBase.metodo5();
objClaseBase.metodo6();
ptrClaseBase -> metodo1();
ptrClaseBase -> metodo2();
ptrClaseBase -> metodo5();
ptrClaseBase -> metodo6();
objptrClaseDerivada.metodo1();
objptrClaseDerivada.metodo2();
objptrClaseDerivada.metodo5();
objptrClaseDerivada.metodo6();
ptrClaseDerivada -> metodo1();
ptrClaseDerivada -> metodo2();
ptrClaseDerivada -> metodo5();
ptrClaseDerivada -> metodo6();
system("pause");
return 0;
}

5. AÑADIR, REEMPLAZAR Y REFINAR


Con lo aprendido hasta ahora se ha supuesto que los atributos y métodos añadidos en una
clase derivada por el programador a los heredados de la clase base son siempre distintos.
Esto es, el conjunto de métodos y atributos de datos definidos por la clase derivada es
disjunto del conjunto de atributos y métodos definidos por las clases base (y antecesoras).
Para definir la situación, se dice que tales métodos y atributos de datos se agregan al

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 137

protocolo de la clase base.


Cuando en una clase derivada se define un método con el mismo nombre que el usado en
la clase base ocurre una situación diferente. El método definido en la clase derivada oculta
o anula al método de la clase base. Cuando se busca un método para usarlo en una
situación dada, se encontrará y usará el método de la clase derivada. Esto es análogo a la
forma en la cual el conocimiento específico de una clase CEntero derivada anula el
conocimiento general de una clase CNumero base. Se debe tener presente que cuando se
envía un mensaje, la búsqueda del método siempre se inicia con el examen de los métodos
asociados al objeto. Si no se encuentra ningún método adecuado, se examinan los métodos
asociados a la clase base inmediata a dicho objeto. Si, una vez más, no se encuentra ningún
método, se examina la clase base inmediata a la clase base anterior, y así sucesivamente
hasta que se llegue a la clase raíz de la jerarquía de clases (en cuyo caso se indica un error)
o se encuentra un método apropiado. Para poder analizar este comportamiento
implantaremos el siguiente código:

//clase CNumero, clase base


#ifndef CNUMERO_H
#define CNUMERO_H
class CNumero
{
public:
CNumero();
CNumero(double);
CNumero* sumar(CNumero*,CNumero*);
CNumero* restar(CNumero*,CNumero*);
void escribir();
private:
double valor;
};
#endif

Implementamos en un archivo separado la operatividad de la case CNumero:

//cnumero.cpp definición de los métodos de CNumero


#include "cnumero.h"
#include <iostream>
using namespace std;
CNumero::CNumero()
{
valor = 0;
}
CNumero::CNumero(double numero)
{
valor = numero;
}
CNumero* CNumero::sumar(CNumero* A,CNumero* B)
{
valor = A->valor + B->valor;
return this;
}
CNumero* CNumero::restar(CNumero* A,CNumero* B)
{
valor = A->valor - B->valor;
return this;
}
void CNumero::escribir()
{
cout << "numero: " << valor << endl;
}

De esta clase creamos una clase en base a derivación:

//clase CEntero, derivada de CNumero

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 138

#ifndef CENTERO_H
#define CENTERO_H
#include "cnumero.h"

class CEntero : public CNumero


{
public:
CEntero();
CEntero(int);
CEntero* sumar(CEntero*,CEntero*);
CEntero* restar(CEntero*,CEntero*);
void escribir();
private:
int valor;
};
#endif

Como en el caso anterior definimos los métodos de la clase en el archivo CEntero.cpp:

//centero.cpp definición de los métodos de CNumero


#include "centero.h"
#include <iostream>
using namespace std;

CEntero::CEntero()
{
valor = 0;
}
CEntero::CEntero(int numero)
{
valor = numero;
}
CEntero* CEntero::sumar(CEntero* A,CEntero* B)
{
valor = A->valor + B->valor;
return this;
}
CEntero* CEntero::restar(CEntero* A,CEntero* B)
{
this->valor = A->valor - B->valor;
return this;
}
void CEntero::escribir()
{
cout << "Entero: " << valor << endl;
}

Para hacer una jerarquía de clase más interesante hacemos una segunda derivación de
CNumero:

//clase CFlotante, derivada de CNumero


#ifndef CFLOTANTE_H
#define CFLOTANTE_H
#include "cnumero.h"

class CFlotante : public CNumero


{
public:
CFlotante();
CFlotante(float);
CFlotante* sumar(CFlotante*,CFlotante*);
CFlotante* restar(CFlotante*,CFlotante*);
void escribir();
private:

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 139

float valor;
};
#endif

Implantamos también las definiciones de métodos en un archivo separado:

//cflotante.cpp definición de los métodos de CFlotante


#include "cflotante.h"
#include <iostream>
using namespace std;
CFlotante::CFlotante()
{
valor = 0;
}
CFlotante::CFlotante(float numero)
{
valor = numero;
}
CFlotante* CFlotante::sumar(CFlotante* A,CFlotante* B)
{
this->valor = A->valor + B->valor;
return (this);
}
CFlotante* CFlotante::restar(CFlotante* A,CFlotante* B)
{
this->valor = A->valor - B->valor;
return this;
}
void CFlotante::escribir()
{
cout << "Flotante: " << valor << endl;
}

Para poder observar su comportamiento implantamos el siguiente programa:

//ejecutaNumero.cpp uso de las clase numericas


#include <iostream>
using namespace std;
#include "cnumero.h"
#include "centero.h"
#include "cflotante.h"
int main()
{
CNumero *objPtr,objB(3.00),objC(1.00),objR;
objPtr =&objR;
CEntero *objPtrEntero, objEnteroB(5), objEnteroC(2);
objPtrEntero = new CEntero;
CFlotante *objPtrFlotante, objFlotanteB(5.1), objFlotanteC(2.6);
objPtrFlotante = new CFlotante;
objPtr -> sumar(&objB,&objC);
objPtr -> escribir();
objPtrEntero -> sumar(&objEnteroB,&objEnteroC);
objPtrEntero -> escribir();
objPtrFlotante -> sumar(&objFlotanteB,&objFlotanteC);
objPtrFlotante -> escribir();
cout << endl;
system("pause");
return 0;
}

Los resultados son los siguientes:

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 140

Es claro que un método de una clase derivada como CEntero que tiene métodos con el
mismo nombre que los métodos de su clase base anulan los métodos de la clase
antecesora. Durante el proceso de búsqueda de un método que se invoca como respuesta
a un mensaje, se encontrará lógicamente al método de la clase derivada antes que al
método de la clase base.

objPtrEntero -> sumar(&objEnteroB,&objEnteroC);


objPtrEntero -> escribir();

Al ejecutar las líneas anteriores se hace el llamado al método CEntero::sumar(CEntero*


A,CEntero* B)
De esta forma un método definido en la clase derivada puede anular un método heredado
por uno de dos propósitos siguientes: Un remplazo de método sustituye totalmente el
método de la clase base durante la ejecución, es decir, el código en la clase base nunca
será ejecutado cuando se manipulan los ejemplares de una clase derivada. El otro tipo de
anulación es un refinamiento de método, el cual incluye, como parte de su comportamiento,
la ejecución del método heredado de la clase base; de esta manera, se preserva y se
aumenta el comportamiento de la clase base.

A. REEMPLAZO
Cuando se llevan a cabo implantaciones de reemplazo y refinamiento se choca con la
dificultad de poder preservar las características del tipo de relación es-un. Esto es, al anular
un método, generalmente no se tiene garantía alguna de que el comportamiento exhibido
por la clase derivada tendrá alguna relación con el de la clase base.
La anulación en C++ se complica por la aplicación concurrente de los conceptos de
anulación, sobrecarga, funciones virtuales (o polimórficas) y constructores.
Los remplazos simples ocurren cuando los argumentos del método de la clase derivada se
acoplan en forma idéntica al tipo y número al método de la clase base y el modificador
virtual se usa en la declaración del método de la misma.

//clase CNumero, clase base con virtual


#ifndef CNUMERO_H
#define CNUMERO_H
class CNumero
{
public:
CNumero();
CNumero(double);
virtual CNumero* sumar(CNumero*,CNumero*);
virtual CNumero* restar(CNumero*,CNumero*);
virtual void escribir();
private:
double valor;
};
#endif

No es necesario incluir virtual en las clases derivadas, probaremos con el código siguiente:

#include <iostream>
using namespace std;
#include "cnumero.h"

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 141

#include "centero.h"
#include "cflotante.h"

int main()
{
CNumero *objPtr,objB(3.00),objC(1.00),objR;
objPtr =&objR;
CEntero *objPtrEntero, objEnteroB(5), objEnteroC(2);
objPtr = new CEntero;
CFlotante objPtrFlotante, objFlotanteB(5.1), objFlotanteC(2.6);
objPtr = &objPtrFlotante;
objPtr -> sumar(0.75,3.9);
objPtr -> escribir();

objPtr -> sumar(&objB,&objC);


objPtr -> escribir();
objPtr -> sumar(&objEnteroB,&objEnteroC);
objPtr -> escribir();
objPtr -> sumar(&objFlotanteB,&objFlotanteC);
objPtr -> escribir();
cout << endl;
system("pause");
return 0;
}

El resultado muestra ahora todo como 0, ¿Qué fue lo que sucedió?, es simple el único
método virtualizado es escribir(), debemos tener en cuenta que cuando se hace el
ocultamiento se debe tener las mismas firmas para los métodos, los métodos presentados
están en una suerte de sobrecarga, pero debido a que el puntero que apunta al objeto es
de la clase base entonces después de buscar un método adecuado se acaba buscando al
método dela clase superior en la jerarquía. Para arreglar esto agregaremos en la clase base
el método siguiente:

CNumero* CNumero::sumar(double A,double B)


{
valor = A + B;
return this;
}

Ahora haremos lo mismo para la clase derivada CFlotante:

CFlotante* CFlotante::sumar(double A,double B)


{
this->valor = A + B;
return (this);
}

Ahora al ejecutar el código del último programa tendremos:

Como los métodos en ambas clases tienen la misma firma entonces se hace la llamada en
función al objeto que envía el mensaje y no en función de la lista de parámetros. Para el

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 142

caso del método escribir(), siempre se llamo en función del objeto apuntado ya que la
firma del método en las tres clases era la misma.

B. REFINAMIENTO
Un método es anulado por una clase derivada con la misma frecuencia tanto para añadir
una mayor funcionalidad como para sustituirla por una funcionalidad nueva. La adición de
la nueva funcionalidad se podría lograr con sólo copiar el código anulado de la clase base.
Sin embargo, semejante enfoque viola algunos de los principios importantes del diseño OO;
por ejemplo, reduce el compartimiento de código, bloquea la ocultación de información y
reduce la confiabilidad debido a que los errores corregidos en la clase base pueden no
transmitirse a las clases derivadas. Así resulta útil que haya algún mecanismo desde dentro
de un método anulado que invoque el mismo método y que "reutilice" de este modo el código
del método anulado. Cuando un método invoca el método anulado de la clase base de esta
manera, se dice que el método refina el de la clase base.
Existen dos técnicas para proporcionar la capacidad de encontrar un método de una
superclase: la primera, usada en Object Pascal, simplemente cambia la clase inicial para la
búsqueda del método, de la clase del receptor a la superclase de la clase del método que
se está ejecutando en el momento; la segunda, usada en C++, nombra en forma explícita
la clase de la cual se va a tomar un método, eliminándose por completo el mecanismo de
búsqueda en tiempo de ejecución y permitiendo que la llamada al procedimiento realice el
paso de mensajes. La segunda técnica es menos elegante que la primera, especialmente
si algún método se copia de una clase a otra no relacionada, o más poderosa al poder
designarse los métodos de ancestros no inmediatos (aun si son anulados en clases
intermedias), y los conflictos entre múltiples superclases pueden resolverse con el uso de
la herencia múltiple. C++ usa la técnica de nombres calificados para dar acceso a métodos
de las clases base en métodos anulados. Un nombre calificado se escribe como un nombre
de clase seguido por un par de dos puntos y el nombre de un procedimiento. El nombre
calificado elimina el mecanismo virtual de paso de mensajes y asegura que el método se
tome de la clase nombrada. Modificaremos el método escribir() en la clase CFlotante
para demostrar este principio.

void CFlotante::escribir()
{
CNumero::escribir();
cout << "Flotante: " << valor << endl;
}

Fácilmente se aprecia que el método heredado de la clase base que está oculto se llama
explícitamente y se ejecuta antes que la impresión en el método de la clase derivada, es
decir la funcionalidad heredada se "refina" agregando características a las ya existentes.

6. RESUMEN
En este capítulo, exploramos cuatro conceptos fundamentales en la programación orientada
a objetos: Clases Abstractas, Interfaces, Funciones Virtuales y Remplazo/Refinamiento.
Las Clases Abstractas nos permiten definir una clase base que no puede ser instanciada
directamente, pero que establece una base común para sus clases derivadas. Las
Interfaces, por otro lado, nos permiten definir un conjunto de métodos que deben ser
implementados por las clases que las implementan.
Las Funciones Virtuales nos brindan la capacidad de redefinir comportamientos en las
clases derivadas, lo que nos permite lograr polimorfismo y escribir código más genérico y
flexible. Por último, el Remplazo y Refinamiento nos ayudan a mantener la coherencia y
flexibilidad en la evolución de nuestros sistemas, permitiéndonos reemplazar o mejorar

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 143

comportamientos en clases derivadas.


A lo largo del capítulo, examinamos ejemplos prácticos y vimos cómo estos conceptos se
aplican en diferentes lenguajes de programación orientados a objetos. Descubrimos cómo
nos ayudan a resolver problemas del mundo real y a escribir código más modular,
reutilizable y mantenible.
En el próximo capítulo, nos adentraremos en el tema de los Patrones de Diseño en la
programación orientada a objetos, explorando soluciones comunes a problemas recurrentes
y brindando pautas para escribir software más robusto y escalable.

IV
(La práctica tiene una duración de 4 horas) ACTIVIDADES

1. EXPERIENCIA DE PRÁCTICA N° 01: CLASES ABSTRACTAS


1. Descripción: En esta experiencia de práctica, los estudiantes trabajarán en la
implementación de una jerarquía de clases utilizando clases abstractas. El objetivo es
comprender cómo se utilizan las clases abstractas para establecer una base común y definir
comportamientos compartidos.
2. Crea una clase abstracta llamada "Figura" que tenga un método abstracto llamado
"calcularArea()". Esta clase servirá como la clase base para diferentes tipos de figuras
geométricas.
3. Crea tres clases derivadas de la clase "Figura": "Círculo", "Rectángulo" y "Triángulo". Cada
una de estas clases debe implementar el método "calcularArea()" de acuerdo a la fórmula
correspondiente para cada figura.
4. Implementa un programa que solicite al usuario que elija una figura (círculo, rectángulo o
triángulo) y proporcione los datos necesarios para calcular su área. Utiliza polimorfismo para
almacenar la figura elegida en una variable de tipo "Figura" y llamar al método
"calcularArea()" correspondiente.
5. Ejecuta el programa y verifica que se calcule correctamente el área de la figura
seleccionada, utilizando la implementación adecuada en la clase derivada.
6. Extiende la jerarquía de clases agregando al menos dos figuras más (por ejemplo,
"Cuadrado" y "Pentágono") y asegúrate de implementar el método "calcularArea()" en cada
clase derivada.
7. Modifica el programa para permitir al usuario seleccionar cualquiera de las figuras
disponibles (incluyendo las nuevas figuras agregadas) y calcular su área.

2. EXPERIENCIA DE PRÁCTICA N° 02: CLASES


COMPLETAMENTE ABSTRACTAS
1. Descripción: En esta experiencia de práctica, los estudiantes trabajarán con clases
completamente abstractas y comprenderán cómo se utilizan para definir una interfaz común
y proporcionar implementaciones parciales de métodos en una jerarquía de clases.
2. Crea una clase completamente abstracta llamada "Forma" sin ninguna implementación de
métodos. Esta clase servirá como una interfaz común para diferentes formas geométricas.
3. Define un método abstracto en la clase "Forma" llamado "calcularPerímetro()". Este método
representará el cálculo del perímetro de una forma geométrica, pero no proporcionará una
implementación específica.
4. Crea dos clases derivadas de "Forma": "Círculo" y "Rectángulo". En cada clase derivada,
implementa el método "calcularPerímetro()" según la fórmula correspondiente para cada
forma geométrica.
5. Implementa un programa que permita al usuario seleccionar una forma (círculo o
rectángulo) y proporcione los datos necesarios para calcular su perímetro. Utiliza
polimorfismo para almacenar la forma seleccionada en una variable de tipo "Forma" y llamar
al método "calcularPerímetro()" correspondiente.
6. Ejecuta el programa y verifica que se calcule correctamente el perímetro de la forma
seleccionada, utilizando la implementación adecuada en la clase derivada.
7. Extiende la jerarquía de clases agregando al menos una forma más (por ejemplo,
"Triángulo") y asegúrate de implementar el método "calcularPerímetro()" en la nueva clase
derivada.
8. Modifica el programa para permitir al usuario seleccionar cualquier forma disponible

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 144

(incluyendo las nuevas formas agregadas) y calcular su perímetro.


9. Con esta experiencia de práctica, los estudiantes podrán comprender cómo se utilizan las
clases completamente abstractas para definir una interfaz común y proporcionar
implementaciones parciales de métodos en una jerarquía de clases. Esto les permitirá
desarrollar una comprensión más profunda de la programación orientada a objetos y su
aplicación en el mundo real.

3. EXPERIENCIA DE PRÁCTICA N° 03: MÉTODOS VIRTUALES


1. Descripción: En esta experiencia de práctica, los estudiantes trabajarán con métodos
virtuales para implementar polimorfismo en una jerarquía de clases. El objetivo es
comprender cómo los métodos virtuales permiten la redefinición de comportamientos en las
clases derivadas.
2. Crea una clase base llamada "Animal" con un método virtual llamado "hacerSonido()". Esta
clase servirá como base para diferentes tipos de animales.
3. Crea al menos dos clases derivadas de "Animal", por ejemplo, "Perro" y "Gato". En cada
clase derivada, redefine el método "hacerSonido()" para que cada animal haga un sonido
diferente.
4. Implementa un programa que cree instancias de diferentes animales (por ejemplo, un perro
y un gato) y llame al método "hacerSonido()". Utiliza polimorfismo para almacenar los
objetos en variables de tipo "Animal" y llamar al método correspondiente.
5. Ejecuta el programa y verifica que cada animal haga el sonido correcto, utilizando la
implementación adecuada en la clase derivada.
6. Agrega al menos una nueva clase derivada, por ejemplo, "Vaca", y redefine el método
"hacerSonido()" para que la vaca haga su propio sonido.
7. Modifica el programa para crear una instancia de la vaca y llamar al método "hacerSonido()".
Verifica que la vaca haga el sonido correcto utilizando la implementación adecuada en la
clase derivada.

4. EXPERIENCIA DE PRÁCTICA N° 04: REMPLAZO Y


REFINAMIENTO
1. En esta experiencia de práctica, los estudiantes trabajarán con los conceptos de remplazo
y refinamiento en la programación orientada a objetos. El objetivo es comprender cómo se
pueden reemplazar o mejorar los comportamientos de una clase base en las clases
derivadas.
2. Crea una clase base llamada "Vehículo" con un método llamado "acelerar()" que imprima
"Acelerando el vehículo".
3. Crea una clase derivada llamada "Coche" que herede de la clase "Vehículo". En la clase
"Coche", redefine el método "acelerar()" para imprimir "Acelerando el coche" en lugar del
mensaje de la clase base.
4. Implementa un programa que cree una instancia de la clase "Coche" y llame al método
"acelerar()". Verifica que se imprima el mensaje de la clase derivada en lugar del mensaje
de la clase base.
5. Agrega otra clase derivada llamada "Motocicleta" que también herede de la clase
"Vehículo". En la clase "Motocicleta", redefine el método "acelerar()" para imprimir
"Acelerando la motocicleta" en lugar del mensaje de la clase base.
6. Modifica el programa para crear una instancia de la clase "Motocicleta" y llamar al método
"acelerar()". Verifica que se imprima el mensaje de la clase derivada en lugar del mensaje
de la clase base y de la clase "Coche".
7. Refina la clase "Coche" añadiendo un nuevo método llamado "encenderLuces()" que
imprima "Luces del coche encendidas". Asegúrate de que la clase "Motocicleta" no tenga
este método.
8. Modifica el programa para llamar al método "encenderLuces()" en una instancia de la clase
"Coche". Verifica que se imprima el mensaje adecuado solo en la clase "Coche" y no en la
clase "Motocicleta".

V
EJERCICIOS

1. Diseña una jerarquía de clases para una aplicación de dibujo. Crea una clase abstracta

Mgter. Ángel Montesinos Sesión N° 08


Lenguajes de Programación II Página 145

llamada "Figura" con métodos abstractos para dibujar y calcular el área de la figura. Luego,
crea clases derivadas como "Círculo", "Rectángulo" y "Triángulo" que implementen estos
métodos de acuerdo con la fórmula correspondiente para cada figura. Crea instancias de
cada figura y prueba los métodos implementados.
2. Define una interfaz llamada "Reproductor" con métodos para reproducir, pausar y detener
la reproducción de un archivo de audio. Luego, crea una clase llamada "ReproductorMP3"
que implemente esta interfaz. Asegúrate de que los métodos de la clase "ReproductorMP3"
realicen las acciones correspondientes. Por último, crea una instancia de "ReproductorMP3"
y prueba los métodos definidos en la interfaz.
3. Crea una clase base llamada "Empleado" con un método virtual llamado "calcularSalario()"
que devuelve el salario mensual del empleado. Luego, crea dos clases derivadas:
"EmpleadoTiempoCompleto" y "EmpleadoMedioTiempo". En cada clase derivada, redefine
el método "calcularSalario()" para calcular el salario de acuerdo a las políticas
correspondientes. Crea instancias de cada tipo de empleado y muestra su salario llamando
al método correspondiente.

VI
CUESTIONARIO

1. ¿Qué es una clase abstracta?


2. ¿Qué es una función virtual?
3. ¿Qué es una función virtual pura?
4. ¿Qué función cumplen las clases abstractas en las jerarquías de clases?
5. ¿Por qué se pueden crear punteros a objetos de clases abstractas?
6. ¿Por qué se pueden crear referencias a objetos de clases abstractas?
7. ¿Por qué se debe separar la interfaz de la implementación?
8. ¿Qué es una clase completamente abstracta?
9. ¿Qué es una interface?
10. ¿Cuándo se debe usar una interface?
11. ¿Cómo se implementa una interface?
12. ¿Cómo se beneficia el polimorfismo con las interfaces?
13. ¿Cómo se beneficia la herencia con las interfaces?
14. ¿Qué es una clase proxy?
15. ¿En qué se diferencian las interfaces de las clases proxy?
16. ¿Cuándo se debe usar un método virtual?
17. ¿Cómo se invoca un método virtual?
18. ¿Cómo se invoca un método virtual en una jerarquía de clases?
19. ¿Qué es una Tabla de funciones virtuales?
20. ¿Cuál es la diferencia entre un método virtual y un método virtual puro?
21. ¿En qué consiste el reemplazo?
22. ¿En qué consiste el refinamiento?
23. ¿Qué ventajas trae la aplicación del reemplazo?
24. ¿Qué sucede si los para metros formales difieren entre los métodos de las clases de la
jerarquía?
25. ¿Qué es la signature o firma del método?

VII
BIBLIOGRAFIA Y REFERENCIAS

• Deitel, Paul J., Deitel, Harvey M., "Cómo Programar en C++", 6ta Edición, Ed. Pearson
Educación, México 2009.
• Stroustrup, Bjarne, "El Lenguaje de Programación C++", 3ra Edición, Adisson Pearson
Educación S.A., Madrid 2002.
• Eckel, Bruce, "Thinking in C++", 2da Edición, Prentice Hall, 2000.

Mgter. Ángel Montesinos Sesión N° 08

También podría gustarte