Skip to content

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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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.0

7.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:

cpp
class ClaseDerivada : public ClaseBase {
    // Miembros adicionales o sobrescritos
};

Ejemplo:

cpp
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 Perro

Polimorfismo ​

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:

cpp
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:

cpp
class Clase {
public:
    Clase() {
        // Código de inicialización
    }
};

Ejemplo con parámetros:

cpp
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 = 10

Destructores ​

Un destructor es una función especial que se ejecuta automáticamente cuando un objeto es destruido. Se utiliza para liberar recursos.

Sintaxis:

cpp
class Clase {
public:
    ~Clase() {
        // Código de limpieza
    }
};

Ejemplo:

cpp
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:

cpp
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:

cpp
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:

cpp
virtual void funcion() = 0;

Ejemplo:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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.