Documentos de Académico
Documentos de Profesional
Documentos de Cultura
de Ingeniería de Sistemas
2023
INFORME DE PRÁCTICAS
LENGUAJES DE
PROGRAMACIÓN II
Introducción
En el ámbito de la programación orientada a objetos (POO), se encuentran varios principios fundamentales que
posibilitan el diseño de sistemas más flexibles, modulares y fáciles de mantener. En este capítulo, examinaremos
cuatro de estos conceptos clave: Clases Abstractas, Interfaces, Funciones Virtuales y Remplazo/Refinamiento.
Iniciaremos comprendiendo 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. Posteriormente, nos adentraremos en el
mundo de las Funciones Virtuales y cómo posibilitan el polimorfismo al permitir la redefinición de comportamientos
en las clases hijas. Finalmente, exploraremos los conceptos de Remplazo y Refinamiento, y cómo contribuyen 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, tales como C++, Java, Python y JavaScript. Observaremos
cómo cada uno de estos conceptos se emplea para resolver problemas del mundo real y cómo nos facilitan la escritura
de código más modular, reutilizable y mantenible.
Resumen
En este capítulo, hemos explorado 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 establece
una base común para las clases derivadas. Por otro lado, las Interfaces nos permiten definir un conjunto de métodos
que deben ser implementados por las clases que las utilicen.
Las Funciones Virtuales nos brindan la capacidad de redefinir comportamientos en las clases derivadas, lo que facilita
el logro de polimorfismo y la escritura de 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 comportamientos en las clases derivadas.
A lo largo del capítulo, hemos analizado ejemplos prácticos y hemos visto cómo estos conceptos se aplican en
diferentes lenguajes de programación orientados a objetos. Hemos descubierto 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 sumergiremos en el tema de los Patrones de Diseño en la programación orientada a
objetos. Exploraremos soluciones comunes a problemas recurrentes y proporcionaremos pautas para escribir
software más robusto y escalable.
MARCO TEÓRICO
1.1 Clases abstractas
¿QUÉ ES? ¿CÓMO SE DECLARA? ¿CÓMO LO USO? ¿CUÁNDO LO USO?
Es la que no instancia / Fig. 25.01: unaclaseabstracta.h Se debe crear una clase hija Se utiliza una clase abstracta
objetos, para tal caso // Declaración de la clase que herede de ella. La clase cuando se desea definir una
se debe evitar que abstracta, se declara método hija debe proporcionar una estructura común y establecer
funcionalmente una virtual puro. implementación para todos un contrato para las clases
clase así no pueda #ifndef los métodos abstractos derivadas. La clase abstracta
hacerlo, esto se UNACLASEABSTRACTA_H definidos en la clase puede contener métodos y
consigue creando #define abstracta. propiedades que son
métodos virtuales UNACLASEABSTRACTA_H compartidos por todas las clases
puros, para convertir Class UnaClaseAbstracta derivadas, proporcionando así
a un método en un {} una base sólida para la herencia
método virtual puro public:
se le iguala a cero en
la declaración, esto UnaClaseAbstracta(int,char,float);
hace que no pueda ser void ingreso();
definida en la clase void salida();
donde se declara. // método virtual puro
virtual void
metodoVirtualPuro() = 0;
int getAtributoEntero();
float getAtributoFloat();
char getAtributoChar();
private:
int atributoEntero;
char atributochar;
float atributoFloat;
};
#endif
Es una clase que solo contiene Varia según el lenguaje de C++ : Cuando se desea definir un
como miembros a métodos programación. class MiClaseHija : public contrato o interfaz que las
virtuales puros, al usarse el En el caso de una clase MiClaseAbstracta { clases concretas deben
mecanismo de separación de la abstracta en C++, se utiliza public: cumplir. Se utiliza para
definición de clase ya la la palabra clave "override" void miMetodo() override { establecer un conjunto de
implementación de métodos d En el caso de una interfaz en // Implementación del métodos que deben ser
forma externa a la clase Java, se utiliza la palabra método abstracto implementados por las
caeremos en la cuenta que no clave "implements" para } clases que la implementan,
se necesita de un archivo .cpp implementar la interfaz en }; sin proporcionar ninguna
una clase. implementación
Java: predeterminada.
public class MiClase
implements MiInterfaz {
// Implementación de los
métodos abstractos de la
interfaz
}
Lo primero que conviene // Fig. 26.01: unaclase.h La clase anterior será usada como clase Cuando se desea definir un
recordar es que C++ no // Declaración de una base convencional, aquí no existen contrato o interfaz que las
dispone de interfaces", y clase cualquiera para métodos virtuales puros, por lo que clases concretas deben
lo que vamos a hacer es herencia dicha clase no es abstracta. Veamos cumplir. Se utiliza para
simular los mismos por #ifndef UNACLASE_H ahora cómo queda el archivo establecer un conjunto de
medio de "clases #define UNACLASE_H "unaclase.cpp”. métodos que deben ser
completamente ClassUnaClase implementados por las
abstractas". { clases que la implementan,
Public: sin proporcionar ninguna
UnaClase(int,char,float); implementación
void ingreso(); predeterminada.
void salida();
int getAtributoEntero();
float getAtributoFloat();
char getAtributoChar();
private:
int atributoEntero;
char atributochar;
float atributoFloat;
};
#endif
1.5 ¿Cómo se declara un método virtual?
¿QUÉ ES? ¿CÓMO SE DECLARA? ¿CÓMO LO USO? ¿CUÁNDO LO USO?
Cuando se trata de Es gestionada por el s transparente para el Se utiliza cuando se desea lograr
métodos virtuales el compilador y no requiere programador y se maneja el polimorfismo y el
compilador debe una declaración explícita de forma interna por el comportamiento dinámico de
vincular la dirección por parte del programador. compilador y el tiempo de los métodos virtuales en una
del método con el ejecución del lenguaje de jerarquía de clases.
objeto invocado, pero programación. Al utilizar 1. Sustitución de objetos:
esto no es posible ya polimorfismo y llamar a 2. Polimorfismo
que únicamente en un método virtual en un 3. Implementación
tiempo de ejecución objeto, el compilador específica de clases
sabremos quién en utiliza la tabla de derivadas:
realidad invoca al funciones virtuales para
método, para determinar la
solucionar este implementación correcta
problema se crea una del método según el tipo
tabla llamada vtable, de objeto concreto.
donde cada objeto de
la clase derivada debe
incluir un puntero a
dicha tabla de
direcciones de
métodos virtuales.
1. 8 AÑADIR
1.7 REEMPLAZO
¿QUÉ ES? ¿CÓMO SE DECLARA? ¿CÓMO LO USO? ¿CUÁNDO LO USO?
Cuando se llevan a Se utiliza la técnica de la ClaseBase* objeto = new Se utiliza cuando se
cabo implantaciones sobreescritura de métodos. ClaseDerivada(); necesita modificar
de reemplazo y Esto implica declarar un objeto->miMetodo(); // completamente el
refinamiento se método con la misma firma Llamada al método comportamiento de
choca con la (nombre, parámetros y tipo reemplazado en la un método heredado
dificultad de poder devuelto) en la clase instancia de la clase en una clase derivada.
preservar las derivada. derivada
características del Personalización del
tipo de relación es- class ClaseBase { comportamiento.
un. Esto es, al anular public:
un método, virtual void miMetodo() { Modificación de
generalmente no se // Implementación de la funcionalidad.
tiene garantía alguna clase base
de que el } Polimorfismo.
comportamiento };
exhibido por la clase
derivada tendrá class ClaseDerivada : public
alguna relación con el ClaseBase {
de la clase base. public:
void miMetodo() override
{
// Nueva
implementación en la clase
derivada
}
};
1. 8 REFINAMIENTO
¿QUÉ ES? ¿CÓMO SE DECLARA? ¿CÓMO LO USO? ¿CUÁNDO LO USO?
ClaseBase::miMetodo(); //
Llamada al método heredado
#include <iostream>
class Animal {
public:
virtual void hacerSonido() const = 0;
};
int main() {
Animal* perro = new Perro();
Animal* gato = new Gato();
Animal* vaca = new Vaca();
perro->hacerSonido();
gato->hacerSonido();
vaca->hacerSonido();
delete perro;
delete gato;
delete vaca;
return 0;
}
#include <iostream>
class Animal {
public:
virtual void hacerSonido() const = 0;
virtual ~Animal() {} // Agregamos un destructor virtual para la clase base
};
int main() {
Animal* perro = new Perro();
Animal* gato = new Gato();
Animal* vaca = new Vaca();
perro->hacerSonido();
gato->hacerSonido();
vaca->hacerSonido();
delete perro;
delete gato;
delete vaca;
return 0;
}
#include <iostream>
class Animal {
public:
virtual void hacerSonido() const = 0;
};
int main() {
Animal* perro = new Perro();
Animal* gato = new Gato();
Animal* vaca = new Vaca();
perro->hacerSonido();
gato->hacerSonido();
vaca->hacerSonido();
delete perro;
delete gato;
delete vaca;
return 0;
}
#include <iostream>
class Vehículo {
public:
virtual void acelerar() const {
std::cout << "Acelerando el vehículo." << std::endl;
}
};
class Coche : public Vehículo {
public:
void acelerar() const override {
std::cout << "Acelerando el coche." << std::endl;
}
Motocicleta motocicleta;
motocicleta.acelerar();
return 0;
}
Ejercicios
1. Ejercicio
#include <iostream>
class Figura {
public:
virtual void dibujar() const = 0;
virtual double calcularArea() const = 0;
};
int main() {
Circulo circulo(5.0);
Rectangulo rectangulo(4.0, 6.0);
Triangulo triangulo(3.0, 7.0);
circulo.dibujar();
std::cout << "Área del círculo: " << circulo.calcularArea() << std::endl;
rectangulo.dibujar();
std::cout << "Área del rectángulo: " << rectangulo.calcularArea() << std::endl;
triangulo.dibujar();
std::cout << "Área del triángulo: " << triangulo.calcularArea() << std::endl;
return 0;
}
2. Ejercicio
#include <iostream>
class Reproductor {
public:
virtual void reproducir() = 0;
virtual void pausar() = 0;
virtual void detener() = 0;
};
reproductor.reproducir();
reproductor.pausar();
reproductor.detener();
return 0;
}
3. Ejercicio
#include <iostream>
class Empleado {
public:
virtual double calcularSalario() const = 0;
};
public:
EmpleadoTiempoCompleto(double base, double b) : salarioBase(base), bono(b) {}
public:
EmpleadoMedioTiempo(double hora, int horas) : salarioHora(hora),
horasTrabajadas(horas) {}
int main() {
EmpleadoTiempoCompleto empleadoTiempoCompleto(2000.0, 500.0);
EmpleadoMedioTiempo empleadoMedioTiempo(15.0, 30);
std::cout << "Salario del empleado a tiempo completo: $" <<
empleadoTiempoCompleto.calcularSalario() << std::endl;
std::cout << "Salario del empleado a medio tiempo: $" <<
empleadoMedioTiempo.calcularSalario() << std::endl;
return 0;
}
Cuestionario
1. ¿Qué es una clase abstracta?
Una clase abstracta es una clase que no puede ser instanciada directamente, sino que se utiliza como una base para
otras clases derivadas. Puede contener métodos con implementaciones concretas, así como métodos abstractos que
deben ser implementados en las clases derivadas. Una clase abstracta proporciona una estructura común y establece
reglas y contratos para las clases hijas.
Una función virtual es una función que puede ser redefinida en las clases derivadas. Permite que el comportamiento
de la función sea específico para cada clase hija, incluso si se llama a través de un puntero o una referencia de la clase
base. Esto facilita el polimorfismo, donde un objeto puede mostrar comportamientos diferentes según el tipo de
objeto con el que se esté trabajando.
Una función virtual pura (también conocida como método virtual puro) es una función virtual que no tiene una
implementación en la clase base. Las clases que tienen una función virtual pura se convierten en clases abstractas y
deben ser implementadas en las clases derivadas. Estas funciones actúan como contratos obligatorios que deben
cumplir las clases hijas.
Las clases abstractas juegan un papel fundamental en las jerarquías de clases al proporcionar una base común para
las clases derivadas. Definen la estructura y los métodos que deben implementarse en las clases hijas, lo que garantiza
la coherencia y la consistencia en el diseño de la jerarquía. Además, permiten el polimorfismo al permitir el uso de
punteros y referencias de la clase base para manipular objetos de las clases derivadas.
Se pueden crear punteros a objetos de clases abstractas porque los punteros no están vinculados a una
implementación concreta. Los punteros a una clase abstracta se utilizan para acceder a objetos de clases derivadas y
permiten manipularlos a través de la clase base, lo que facilita el polimorfismo y la escritura de código más genérico.
Las referencias a objetos de clases abstractas también son posibles porque proporcionan un nivel de indirección similar
a los punteros. Las referencias permiten manipular objetos de clases derivadas a través de la clase base de manera
transparente, lo que facilita la implementación de polimorfismo y la escritura de código más claro y legible.
Una clase completamente abstracta es una clase que solo contiene métodos abstractos (funciones virtuales puras) y
no tiene implementaciones concretas. Estas clases solo se utilizan como bases para otras clases y no se pueden
instanciar directamente. Su propósito principal es proporcionar una estructura común y establecer contratos para las
clases derivadas.
Una interfaz es una colección de métodos abstractos que definen un conjunto de comportamientos que una clase
debe implementar. Las interfaces establecen un contrato que las clases deben cumplir y permiten una programación
orientada a interfaces. Proporcionan una forma de lograr la herencia múltiple y permiten una mayor flexibilidad y
reutilización del código al separar la definición de la implementación.
Se debe usar una interfaz cuando se necesita definir un conjunto común de métodos que deben implementar varias
clases independientes entre sí, pero que comparten un comportamiento similar. Las interfaces permiten que
diferentes clases cumplan un contrato común y facilitan la intercambiabilidad y el polimorfismo.
Una interfaz se implementa en una clase mediante la declaración de la implementación de cada uno de los métodos
definidos en la interfaz. La clase debe proporcionar una implementación concreta para cada método declarado en la
interfaz. Esto asegura que la clase cumpla con el contrato establecido por la interfaz.
El polimorfismo se beneficia con las interfaces al permitir que diferentes objetos que implementan la misma interfaz
sean tratados de manera uniforme. Esto significa que se puede llamar a los métodos de la interfaz en objetos de
diferentes clases, siempre que implementen esa interfaz. Esto proporciona una mayor flexibilidad y reutilización del
código, ya que se pueden utilizar objetos intercambiables sin preocuparse por su tipo concreto.
La herencia se beneficia con las interfaces al permitir que una clase implemente múltiples interfaces. Esto facilita la
reutilización de código al permitir que una clase herede funcionalidades de varias fuentes a través de la
implementación de diferentes interfaces. Esto promueve la modularidad y la flexibilidad en el diseño de clases y
jerarquías de herencia.
Una clase proxy es una clase que actúa como intermediario o representante de otra clase. Proporciona una interfaz
similar a la clase que está representando y puede agregar funcionalidad adicional, como control de acceso, registro o
implementación perezosa. Los objetos de la clase proxy se utilizan como sustitutos de los objetos originales y controlan
el acceso a ellos.
Las interfaces definen un contrato que una clase debe cumplir, especificando los métodos que deben ser
implementados. Por otro lado, las clases proxy son objetos que actúan como intermediarios y proporcionan una
interfaz similar a la clase que representan, pero pueden agregar funcionalidad adicional. Mientras que una interfaz se
enfoca en la definición de comportamiento, las clases proxy se centran en el control y la manipulación de los objetos
representados.
Se debe usar un método virtual cuando se desea permitir que las clases derivadas redefinan su comportamiento. Esto
facilita la personalización y la adaptación de los métodos en las clases hijas según sus necesidades específicas. Los
métodos virtuales proporcionan una forma de lograr el polimorfismo y permiten escribir código más genérico y
flexible.
Un método virtual se invoca utilizando un puntero o una referencia a la clase base que apunta a un objeto de la clase
derivada. Esto permite que se ejecute la implementación del método correspondiente a la clase derivada en lugar de
la implementación de la clase base. La llamada se resuelve en tiempo de ejecución, lo que permite que el polimorfismo
y el comportamiento dinámico se apliquen adecuadamente.
Para invocar un método virtual en una jerarquía de clases, se utiliza la sintaxis de puntero o referencia a la clase base.
Si el método es virtual y ha sido redefinido en una clase derivada, la implementación correspondiente de la clase
derivada se ejecutará en tiempo de ejecución. Esto permite que el comportamiento polimórfico se aplique incluso
cuando se trabaja con punteros o referencias de la clase base.
Una Tabla de funciones virtuales (también conocida como tabla de métodos virtuales o tabla de dispatch) es una
estructura de datos utilizada para resolver en tiempo de ejecución qué implementación de un método virtual debe
invocarse en una jerarquía de clases. Cada clase que contiene al menos un método virtual tiene su propia tabla de
funciones virtuales que almacena punteros a las implementaciones de los métodos virtuales en esa clase.
La diferencia entre un método virtual y un método virtual puro (función virtual pura) radica en su implementación en
la clase base. Un método virtual tiene una implementación concreta en la clase base, lo que permite llamarlo
directamente. Por otro lado, un método virtual puro no tiene implementación en la clase base y debe ser
implementado en las clases derivadas. Esto hace que la clase base se convierta en una clase abstracta y no pueda ser
instanciada directamente.
El reemplazo consiste en sustituir o modificar el comportamiento de un método en una clase derivada. Esto se logra
al redefinir el método virtual en la clase hija, proporcionando una nueva implementación que reemplaza la
implementación heredada de la clase base. El reemplazo permite personalizar el comportamiento de los métodos en
las clases derivadas sin afectar la funcionalidad de la clase base.
El refinamiento consiste en mejorar o extender la funcionalidad de un método en una clase derivada. Esto se logra al
redefinir el método virtual en la clase hija y agregar funcionalidad adicional a la implementación existente en la clase
base. El refinamiento permite enriquecer el comportamiento de los métodos heredados y adaptarlos a las necesidades
específicas de la clase derivada.
24. ¿Qué sucede si los para metros formales difieren entre los métodos de las clases de la
jerarquía?
Si los parámetros formales difieren entre los métodos de las clases de la jerarquía, se produce una incompatibilidad y
la llamada al método puede ser ambigua o incorrecta. Para evitar esto, es importante asegurarse de que los métodos
en la jerarquía de clases tengan una firma consistente, es decir, que tengan los mismos tipos de parámetros y el mismo
tipo de valor de retorno. Esto garantiza que se pueda llamar al método adecuado según el tipo de objeto con el que
se esté trabajando.
La signature o firma del método se refiere a la combinación de su nombre, tipo de parámetros y tipo de valor de
retorno. La firma de un método es única dentro de una clase y se utiliza para identificar y resolver llamadas a métodos
en tiempo de compilación. La signature del método es parte de su interfaz y define cómo se llama y qué tipo de valores
Conclusiones
1. Los conceptos de Clases Abstractas e Interfaces son fundamentales en la POO para establecer una base común y
definir contratos entre las clases derivadas. Estos conceptos promueven la modularidad y la reutilización de código al
proporcionar una estructura y un conjunto de métodos que deben implementarse.
2. Las Funciones Virtuales permiten la redefinición de comportamientos en las clases derivadas, lo que posibilita el
polimorfismo y la escritura de código más flexible y genérico. Esto facilita la adaptación del comportamiento de los
objetos según el contexto en el que se utilicen.
3. El Remplazo y Refinamiento son conceptos que contribuyen a mantener la coherencia y flexibilidad en la evolución
de los sistemas. Permiten reemplazar o mejorar comportamientos en las clases derivadas sin afectar la estructura o el
funcionamiento de las clases base, lo que resulta en un código más adaptable y escalable.
4. A través de ejemplos prácticos y el estudio de diferentes lenguajes de programación orientados a objetos como C++,
Java, Python y JavaScript, se demuestra cómo estos conceptos se aplican en la práctica y ayudan a resolver problemas
del mundo real. Además, se destaca la importancia de escribir código modular, reutilizable y mantenible para facilitar
el desarrollo y la evolución de los sistemas.