Capítulo 7: Programación Orientada a Objetos
7.1 Nivel Introductorio
Clases y objetos
La Programación Orientada a Objetos (POO) es un paradigma que utiliza objetos y clases para modelar entidades del mundo real y sus interacciones. En C++, la POO permite crear programas más organizados, modulares y fáciles de mantener.
¿Qué es una clase?
Una clase es un molde o plantilla que define atributos (datos) y métodos (funciones) comunes a un conjunto de objetos. En esencia, una clase describe las propiedades y comportamientos que tendrán los objetos creados a partir de ella.
Sintaxis básica de una clase:
class NombreClase {
public:
// Atributos y métodos públicos
private:
// Atributos y métodos privados
};¿Qué es un objeto?
Un objeto es una instancia de una clase. Representa una entidad concreta que posee los atributos y métodos definidos en su clase.
Ejemplo:
Supongamos que queremos modelar un Coche:
class Coche {
public:
std::string marca;
std::string modelo;
int año;
void acelerar() {
std::cout << "El coche está acelerando." << std::endl;
}
void frenar() {
std::cout << "El coche está frenando." << std::endl;
}
};
// Creación de un objeto
Coche miCoche;
miCoche.marca = "Toyota";
miCoche.modelo = "Corolla";
miCoche.año = 2020;
miCoche.acelerar(); // Salida: El coche está acelerando.Encapsulación
La encapsulación es el principio que consiste en ocultar los detalles internos de una clase y exponer solo lo necesario a través de una interfaz pública. Esto se logra mediante los modificadores de acceso public, private y protected.
Beneficios de la encapsulación:
- Protección de datos: Evita acceso directo a los atributos, previniendo modificaciones no deseadas.
- Control de acceso: Permite controlar cómo se interactúa con los datos de la clase.
- Facilita el mantenimiento: Los cambios internos no afectan a otros componentes del programa.
Ejemplo de encapsulación:
class Persona {
private:
std::string nombre;
int edad;
public:
void setNombre(const std::string& nuevoNombre) {
nombre = nuevoNombre;
}
std::string getNombre() const {
return nombre;
}
void setEdad(int nuevaEdad) {
if (nuevaEdad >= 0) {
edad = nuevaEdad;
}
}
int getEdad() const {
return edad;
}
};
// Uso de la clase
Persona persona1;
persona1.setNombre("Carlos");
persona1.setEdad(30);
std::cout << "Nombre: " << persona1.getNombre() << std::endl;
std::cout << "Edad: " << persona1.getEdad() << std::endl;Miembros de clase: atributos y métodos
Atributos (variables miembro)
Los atributos son variables que almacenan el estado de un objeto. Se definen dentro de la clase y pueden ser de cualquier tipo.
Ejemplo:
class Rectangulo {
public:
double ancho;
double alto;
};Métodos (funciones miembro)
Los métodos son funciones que definen el comportamiento de los objetos. Pueden manipular los atributos y realizar operaciones.
Ejemplo:
class Rectangulo {
public:
double ancho;
double alto;
double calcularArea() {
return ancho * alto;
}
};
// Uso de la clase
Rectangulo rect;
rect.ancho = 5.0;
rect.alto = 3.0;
std::cout << "Área: " << rect.calcularArea() << std::endl; // Área: 15.07.2 Nivel Intermedio
Herencia y polimorfismo
Herencia
La herencia permite crear nuevas clases (clases derivadas) basadas en clases existentes (clases base), heredando atributos y métodos. Facilita la reutilización de código y establece relaciones jerárquicas.
Sintaxis básica:
class ClaseDerivada : public ClaseBase {
// Miembros adicionales o sobrescritos
};Ejemplo:
class Animal {
public:
void comer() {
std::cout << "El animal está comiendo." << std::endl;
}
};
class Perro : public Animal {
public:
void ladrar() {
std::cout << "El perro está ladrando." << std::endl;
}
};
// Uso
Perro miPerro;
miPerro.comer(); // Heredado de Animal
miPerro.ladrar(); // Método propio de PerroPolimorfismo
El polimorfismo permite que objetos de diferentes clases derivadas sean tratados como objetos de la clase base, ejecutando el comportamiento específico de cada clase.
Funciones virtuales y polimorfismo dinámico:
class Animal {
public:
virtual void hacerSonido() {
std::cout << "El animal hace un sonido." << std::endl;
}
};
class Gato : public Animal {
public:
void hacerSonido() override {
std::cout << "El gato dice: Miau." << std::endl;
}
};
class Perro : public Animal {
public:
void hacerSonido() override {
std::cout << "El perro dice: Guau." << std::endl;
}
};
// Uso
Animal* animales[2];
animales[0] = new Gato();
animales[1] = new Perro();
for (int i = 0; i < 2; i++) {
animales[i]->hacerSonido();
}
// Liberación de memoria
delete animales[0];
delete animales[1];Salida:
El gato dice: Miau.
El perro dice: Guau.Constructores y destructores
Constructores
Un constructor es una función especial que se ejecuta automáticamente al crear un objeto. Se utiliza para inicializar los atributos.
Sintaxis:
class Clase {
public:
Clase() {
// Código de inicialización
}
};Ejemplo con parámetros:
class Punto {
public:
int x;
int y;
// Constructor por defecto
Punto() : x(0), y(0) {}
// Constructor con parámetros
Punto(int xVal, int yVal) : x(xVal), y(yVal) {}
};
// Uso
Punto p1; // x = 0, y = 0
Punto p2(5, 10); // x = 5, y = 10Destructores
Un destructor es una función especial que se ejecuta automáticamente cuando un objeto es destruido. Se utiliza para liberar recursos.
Sintaxis:
class Clase {
public:
~Clase() {
// Código de limpieza
}
};Ejemplo:
class Archivo {
private:
std::fstream file;
public:
Archivo(const std::string& nombre) {
file.open(nombre, std::ios::out);
}
~Archivo() {
if (file.is_open()) {
file.close();
}
}
};Modificadores de acceso: public, private, protected
public: Miembros accesibles desde cualquier parte.private: Miembros accesibles solo desde dentro de la clase.protected: Miembros accesibles desde la clase y sus derivadas.
Ejemplo:
class Base {
public:
int datoPublico;
protected:
int datoProtegido;
private:
int datoPrivado;
};
class Derivada : public Base {
public:
void mostrar() {
datoPublico = 1; // Accesible
datoProtegido = 2; // Accesible
// datoPrivado = 3; // Error: no accesible
}
};7.3 Nivel Avanzado
Sobrecarga de operadores
La sobrecarga de operadores permite redefinir el comportamiento de los operadores para objetos de clases personalizadas.
Ejemplo: Sobrecarga del operador + en una clase Vector2D:
class Vector2D {
public:
double x;
double y;
Vector2D(double xVal = 0, double yVal = 0) : x(xVal), y(yVal) {}
// Sobrecarga del operador +
Vector2D operator+(const Vector2D& otro) const {
return Vector2D(x + otro.x, y + otro.y);
}
// Sobrecarga del operador <<
friend std::ostream& operator<<(std::ostream& os, const Vector2D& vec) {
os << "(" << vec.x << ", " << vec.y << ")";
return os;
}
};
// Uso
Vector2D v1(1.0, 2.0);
Vector2D v2(3.0, 4.0);
Vector2D v3 = v1 + v2;
std::cout << "v1 + v2 = " << v3 << std::endl; // Salida: v1 + v2 = (4, 6)Clases abstractas e interfaces
Clases abstractas
Una clase abstracta contiene al menos una función virtual pura y no puede instanciarse directamente.
Definición de una función virtual pura:
virtual void funcion() = 0;Ejemplo:
class Figura {
public:
virtual double calcularArea() = 0; // Función virtual pura
};
class Circulo : public Figura {
private:
double radio;
public:
Circulo(double r) : radio(r) {}
double calcularArea() override {
return 3.1416 * radio * radio;
}
};
class Rectangulo : public Figura {
private:
double ancho;
double alto;
public:
Rectangulo(double a, double h) : ancho(a), alto(h) {}
double calcularArea() override {
return ancho * alto;
}
};
// Uso
Figura* f1 = new Circulo(5.0);
Figura* f2 = new Rectangulo(4.0, 6.0);
std::cout << "Área del círculo: " << f1->calcularArea() << std::endl;
std::cout << "Área del rectángulo: " << f2->calcularArea() << std::endl;
delete f1;
delete f2;Interfaces
En C++, las interfaces se implementan utilizando clases abstractas que solo tienen funciones virtuales puras.
Ejemplo:
class ISerializable {
public:
virtual void guardar(const std::string& nombreArchivo) = 0;
virtual void cargar(const std::string& nombreArchivo) = 0;
};
class Configuracion : public ISerializable {
public:
void guardar(const std::string& nombreArchivo) override {
// Código para guardar configuración
}
void cargar(const std::string& nombreArchivo) override {
// Código para cargar configuración
}
};Patrones de diseño (Singleton, Factory, Observer)
Singleton
El patrón Singleton asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella.
Implementación:
class Logger {
private:
static Logger* instancia;
// Constructor privado
Logger() {}
public:
static Logger* getInstancia() {
if (instancia == nullptr) {
instancia = new Logger();
}
return instancia;
}
void log(const std::string& mensaje) {
std::cout << "Log: " << mensaje << std::endl;
}
};
// Inicialización del miembro estático
Logger* Logger::instancia = nullptr;
// Uso
Logger::getInstancia()->log("Mensaje de prueba");Factory (Fábrica)
El patrón Factory delega la creación de objetos a una clase fábrica, permitiendo crear objetos sin exponer la lógica de instanciación.
Ejemplo:
class Transporte {
public:
virtual void mover() = 0;
};
class Bicicleta : public Transporte {
public:
void mover() override {
std::cout << "La bicicleta se mueve a pedal." << std::endl;
}
};
class Automovil : public Transporte {
public:
void mover() override {
std::cout << "El automóvil se mueve a motor." << std::endl;
}
};
class TransporteFactory {
public:
static Transporte* crearTransporte(const std::string& tipo) {
if (tipo == "Bicicleta") {
return new Bicicleta();
} else if (tipo == "Automovil") {
return new Automovil();
} else {
return nullptr;
}
}
};
// Uso
Transporte* t = TransporteFactory::crearTransporte("Bicicleta");
if (t != nullptr) {
t->mover();
delete t;
}Observer (Observador)
El patrón Observer permite que objetos suscriptores sean notificados automáticamente cuando el estado de otro objeto cambia.
Implementación:
#include <vector>
class Observador {
public:
virtual void actualizar() = 0;
};
class Sujeto {
private:
std::vector<Observador*> observadores;
public:
void agregarObservador(Observador* obs) {
observadores.push_back(obs);
}
void notificar() {
for (Observador* obs : observadores) {
obs->actualizar();
}
}
void cambiarEstado() {
// Cambios en el estado del sujeto
notificar();
}
};
class ObservadorConcreto : public Observador {
public:
void actualizar() override {
std::cout << "El observador ha sido notificado." << std::endl;
}
};
// Uso
Sujeto sujeto;
ObservadorConcreto obs1, obs2;
sujeto.agregarObservador(&obs1);
sujeto.agregarObservador(&obs2);
sujeto.cambiarEstado();Salida:
El observador ha sido notificado.
El observador ha sido notificado.