Saltar a contenido

Clases I: Definiciones y Conceptos de POO

Introducción

Hasta ahora hemos visto principalmente características de C++ que también existen en C.

A partir de este capítulo, entramos en el corazón de C++: las clases y la Programación Orientada a Objetos (POO).

Cambio de paradigma: La forma de pensar y estructurar programas cambia completamente. En lugar de separar datos y funciones, ahora los agrupamos en entidades llamadas objetos.


Paradigma de Programación

Programación Estructurada (C tradicional)

// Datos separados
struct Punto {
    int x;
    int y;
};

// Funciones separadas
void mover_punto(struct Punto* p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

int main() {
    struct Punto p = {0, 0};
    mover_punto(&p, 10, 20);  // Función externa
}

Características: - Datos y funciones están separados - Las funciones operan sobre los datos - Los datos son pasivos

Programación Orientada a Objetos (C++)

// Datos y funciones juntos
class Punto {
    int x;
    int y;
public:
    void mover(int dx, int dy) {
        x += dx;
        y += dy;
    }
};

int main() {
    Punto p;
    p.mover(10, 20);  // Método del objeto
}

Características: - Datos y funciones están agrupados en una unidad (objeto) - Las funciones son parte del objeto - Los objetos son activos (tienen comportamiento)

Conceptos Fundamentales de POO

1. Programación Orientada a Objetos (POO)

Definición: Paradigma de programación que agrupa datos y procedimientos en una única entidad llamada objeto.

Siglas: - Español: POO - Inglés: OOP (Object-Oriented Programming)

Principio básico:

"Cada programa es un objeto, que a su vez está formado de objetos que se relacionan entre sí."

Ventajas: - ✅ Encapsulación: Datos protegidos - ✅ Modularidad: Código organizado - ✅ Reutilización: Herencia y composición - ✅ Mantenibilidad: Cambios localizados

Nota importante: La programación estructurada no desaparece, se refuerza y complementa con POO.

2. Objeto

Definición: Unidad que engloba en sí misma: - Datos (atributos, propiedades, campos) - Procedimientos (funciones, métodos)

Analogía del mundo real:

Objeto: Automóvil
├── Datos:
│   ├── marca: "Toyota"
│   ├── modelo: "Corolla"
│   ├── velocidad: 80 km/h
│   └── combustible: 45 litros
└── Métodos:
    ├── acelerar()
    ├── frenar()
    ├── girar()
    └── abastecer()

Ejemplo en C++:

class Automovil {
    // Datos (privados)
    string marca;
    string modelo;
    int velocidad;
    float combustible;

public:
    // Métodos (públicos)
    void acelerar(int incremento) {
        velocidad += incremento;
    }

    void frenar(int decremento) {
        velocidad -= decremento;
    }

    void abastecer(float litros) {
        combustible += litros;
    }
};

Comparación:

Programación Tradicional POO
Datos y funciones separados Datos y funciones juntos
struct + funciones externas class con métodos
Datos expuestos Datos protegidos (encapsulación)

3. Clase

Definición: Patrón o plantilla para crear objetos.

Analogía: - Clase = Plano de una casa - Objeto = Casa construida según el plano

// Clase: el plano
class Casa {
    int habitaciones;
    float area;
public:
    void construir();
};

// Objetos: casas construidas según el plano
Casa casa1;  // Instancia 1
Casa casa2;  // Instancia 2
Casa casa3;  // Instancia 3

Relación Clase-Objeto:

         Clase Perro
              │ (es el patrón para)
    ┌─────────┼─────────┐
    │         │         │
  Objeto    Objeto    Objeto
  perro1    perro2    perro3
  (Firulais)(Rex)    (Max)

Diferencia clave:

Clase Objeto
Declaración / Plantilla Instancia / Ejemplar
No ocupa memoria (solo definición) Ocupa memoria
No puede recibir mensajes Puede recibir mensajes
Define estructura y comportamiento Tiene estado y comportamiento

Ejemplo:

// CLASE: definición, no es un objeto
class Rectangulo {
    int ancho;
    int alto;
public:
    int area() { return ancho * alto; }
};

// OBJETOS: instancias de la clase
Rectangulo r1;  // Objeto 1 en memoria
Rectangulo r2;  // Objeto 2 en memoria
Rectangulo r3;  // Objeto 3 en memoria

// Cada objeto tiene su propia copia de los datos
// Pero comparten la misma definición de métodos

4. Método

Definición: Función o procedimiento que pertenece a un objeto/clase.

En C++: Un método es simplemente una función miembro de una clase.

Terminología:

Contexto Término
POO general Método
C++ técnico Función miembro
Conversación Ambos son válidos

Ejemplo:

class Contador {
    int valor;

public:
    // Métodos
    void incrementar() {    // Método 1
        valor++;
    }

    void decrementar() {    // Método 2
        valor--;
    }

    int obtener() {         // Método 3
        return valor;
    }
};

int main() {
    Contador c;
    c.incrementar();  // Llamada a método
    c.incrementar();
    c.decrementar();
    int v = c.obtener();
}

Características: - Operan sobre los datos del objeto - Tienen acceso a los miembros privados - Pueden modificar el estado interno del objeto

5. Mensaje

Definición: Modo en que se comunican e interrelacionan los objetos entre sí.

En C++: Un mensaje es una llamada a un método de un objeto.

Analogía:

Objeto Persona                  Objeto Perro
     │                               │
     │ Mensaje: "ladra()"            │
     ├──────────────────────────────>│
     │                               │
     │                          [Ejecuta método]
     │                               │
     │     Respuesta: "Guau!"        │
     │<──────────────────────────────┤

Ejemplo en código:

class Calculadora {
    int resultado;
public:
    void sumar(int a, int b) {
        resultado = a + b;
    }

    int obtener_resultado() {
        return resultado;
    }
};

int main() {
    Calculadora calc;

    // Enviar mensaje "sumar" al objeto calc
    calc.sumar(5, 3);

    // Enviar mensaje "obtener_resultado"
    int res = calc.obtener_resultado();

    cout << res << endl;  // Output: 8
}

Paso a paso: 1. calc.sumar(5, 3) → Enviamos mensaje "sumar" con parámetros 5 y 3 2. El objeto calc procesa el mensaje ejecutando su método sumar() 3. calc.obtener_resultado() → Enviamos mensaje "obtener_resultado" 4. El objeto responde devolviendo el valor

Comunicación entre objetos:

class Motor {
public:
    void encender() { /* ... */ }
    void apagar() { /* ... */ }
};

class Automovil {
    Motor motor;  // Objeto Motor dentro de Automovil
public:
    void arrancar() {
        motor.encender();  // Automovil envía mensaje a Motor
    }

    void detener() {
        motor.apagar();    // Automovil envía mensaje a Motor
    }
};

6. Interfaz

Definición: Parte del objeto que es visible para el resto de los objetos. Conjunto de métodos (y a veces datos) públicos.

Analogía: La interfaz de un objeto es como los controles de un aparato: - TV: botones de encender, cambiar canal, volumen → Interfaz - Circuitos internos → Implementación privada

Ejemplo:

class CuentaBancaria {
    // PARTE PRIVADA (no es interfaz)
    double saldo;
    string numero_cuenta;

    // Método privado
    bool validar_operacion(double monto) {
        return monto > 0 && saldo >= monto;
    }

public:
    // INTERFAZ PÚBLICA
    void depositar(double monto) {    // ✅ Visible
        saldo += monto;
    }

    bool retirar(double monto) {      // ✅ Visible
        if (validar_operacion(monto)) {
            saldo -= monto;
            return true;
        }
        return false;
    }

    double consultar_saldo() {        // ✅ Visible
        return saldo;
    }
};

int main() {
    CuentaBancaria cuenta;

    // Solo podemos usar la INTERFAZ PÚBLICA
    cuenta.depositar(1000);         // ✅ OK
    cuenta.retirar(500);            // ✅ OK
    double s = cuenta.consultar_saldo();  // ✅ OK

    // No podemos acceder a la parte privada
    // cuenta.saldo = 9999999;      // ❌ ERROR: privado
    // cuenta.validar_operacion(100); // ❌ ERROR: privado
}

Componentes de la interfaz:

┌─────────────────────────────────┐
│         Clase                   │
├─────────────────────────────────┤
│ INTERFAZ PÚBLICA (visible)      │
│ - métodos públicos              │
│ - datos públicos (raro)         │
├─────────────────────────────────┤
│ IMPLEMENTACIÓN PRIVADA (oculta) │
│ - datos privados                │
│ - métodos privados auxiliares   │
└─────────────────────────────────┘

Ventajas de una buena interfaz: - 🔒 Encapsulación: Oculta detalles internos - 🛡️ Protección: Evita uso indebido - 🔧 Mantenibilidad: Puedes cambiar la implementación sin afectar a los usuarios - 📖 Claridad: Define claramente qué puede hacer el objeto

7. Herencia

Definición: Capacidad de crear nuevas clases basándose en clases existentes, aprovechando datos y métodos, descartando otros y añadiendo nuevos.

Terminología: - C++ técnico: Derivación de clases - POO general: Herencia

Analogía biológica:

        Animal (clase base)
    ┌──────┴──────┐
    │             │
Mamífero      Reptil
    │             │
  ┌─┴─┐         ┌─┴─┐
  │   │         │   │
Perro Gato  Serpiente Lagarto

Ejemplo en C++:

// CLASE BASE
class Vehiculo {
protected:
    int velocidad;
    string marca;

public:
    void acelerar(int v) {
        velocidad += v;
    }

    void frenar(int v) {
        velocidad -= v;
    }
};

// CLASE DERIVADA 1
class Automovil : public Vehiculo {
    int num_puertas;

public:
    void abrir_maletero() {
        // Método específico de Automovil
    }

    // Hereda: acelerar(), frenar(), velocidad, marca
};

// CLASE DERIVADA 2
class Motocicleta : public Vehiculo {
    bool tiene_sidecar;

public:
    void hacer_caballito() {
        // Método específico de Motocicleta
    }

    // Hereda: acelerar(), frenar(), velocidad, marca
};

Uso:

int main() {
    Automovil coche;
    coche.acelerar(50);     // ✅ Heredado de Vehiculo
    coche.abrir_maletero(); // ✅ Propio de Automovil

    Motocicleta moto;
    moto.acelerar(80);      // ✅ Heredado de Vehiculo
    moto.hacer_caballito(); // ✅ Propio de Motocicleta
}

Conceptos clave:

Término Significado
Clase base Clase de la que se hereda (Vehiculo)
Clase derivada Clase que hereda (Automovil, Motocicleta)
Heredar Recibir características de la clase base
Sobrescribir Redefinir un método heredado
Extender Añadir nuevos métodos/datos

Ventajas: - ♻️ Reutilización: No duplicar código - 🎯 Especialización: Añadir funcionalidad específica - 🔄 Mantenibilidad: Cambios en la base afectan a todas las derivadas - 📐 Estructura lógica: Refleja relaciones del mundo real

Ejemplo más completo:

class Figura {
protected:
    int x, y;  // Posición

public:
    void mover(int dx, int dy) {
        x += dx;
        y += dy;
    }

    virtual double area() = 0;  // Método abstracto
};

class Rectangulo : public Figura {
    int ancho, alto;

public:
    double area() override {
        return ancho * alto;
    }
};

class Circulo : public Figura {
    double radio;

public:
    double area() override {
        return 3.14159 * radio * radio;
    }
};

Herencia múltiple:

class Anfibio : public Animal, public Acuatico {
    // Hereda de DOS clases base
};

8. Jerarquía

Definición: Orden de subordinación en un sistema de clases.

Características: - Herencia en un solo sentido: de base a derivada - Forma estructuras de árbol - Siempre se puede retroceder a la(s) clase(s) base

Ejemplo de jerarquía:

               Ser Vivo
        ┌─────────┴─────────┐
        │                   │
    Vertebrado          Invertebrado
    ┌───┴───┐
    │       │
Mamífero   Reptil
  ┌─┴─┐
  │   │
Felino Canino
  │     │
┌─┴─┐ ┌─┴─┐
│   │ │   │
Gato León Perro Lobo

En código:

class SerVivo {
    bool esta_vivo;
public:
    virtual void respirar() = 0;
};

class Vertebrado : public SerVivo {
protected:
    int num_vertebras;
public:
    void respirar() override { /* ... */ }
};

class Mamifero : public Vertebrado {
protected:
    bool tiene_pelo;
public:
    void amamantar() { /* ... */ }
};

class Felino : public Mamifero {
protected:
    bool tiene_garras;
public:
    void cazar() { /* ... */ }
};

class Gato : public Felino {
public:
    void maullar() { cout << "Miau!" << endl; }

    // Hereda TODO de la jerarquía:
    // - esta_vivo (de SerVivo)
    // - num_vertebras (de Vertebrado)
    // - tiene_pelo (de Mamifero)
    // - tiene_garras (de Felino)
    // + métodos de todas las clases
};

Niveles jerárquicos:

Gato miGato;

// Todos estos son válidos:
Gato* p1 = &miGato;        // Como Gato
Felino* p2 = &miGato;      // Como Felino
Mamifero* p3 = &miGato;    // Como Mamifero
Vertebrado* p4 = &miGato;  // Como Vertebrado
SerVivo* p5 = &miGato;     // Como SerVivo

Importancia: - Permite polimorfismo (siguiente concepto) - Facilita diseño modular - Refleja relaciones naturales

9. Polimorfismo

Definición literal: "Cualidad de lo que tiene o puede tener distintas formas"

Definición POO: Propiedad según la cual un mismo objeto puede considerarse como perteneciente a distintas clases.

Idea básica: Un objeto de una clase derivada puede tratarse como si fuera de cualquier clase de su jerarquía.

Ejemplo conceptual:

Jerarquía: SerVivo → Vertebrado → Mamífero → Felino → Gato

Un objeto "Gato" puede tratarse como:
- Gato       (su clase real)
- Felino     (puede actuar como felino genérico)
- Mamífero   (puede actuar como mamífero genérico)
- Vertebrado (puede actuar como vertebrado genérico)
- SerVivo    (puede actuar como ser vivo genérico)

Ejemplo práctico:

class Felino {
public:
    virtual void hacer_sonido() = 0;  // Método abstracto
    virtual void cazar() { /* ... */ }
};

class Gato : public Felino {
public:
    void hacer_sonido() override {
        cout << "Miau!" << endl;
    }
};

class Leon : public Felino {
public:
    void hacer_sonido() override {
        cout << "Roar!" << endl;
    }
};

class Tigre : public Felino {
public:
    void hacer_sonido() override {
        cout << "Grrr!" << endl;
    }
};

// POLIMORFISMO EN ACCIÓN
void alimentar_felino(Felino* f) {  // Acepta CUALQUIER felino
    f->hacer_sonido();
    f->cazar();
}

int main() {
    Gato g;
    Leon l;
    Tigre t;

    // Todos se pueden tratar como "Felino"
    alimentar_felino(&g);  // ✅ Gato como Felino
    alimentar_felino(&l);  // ✅ Leon como Felino
    alimentar_felino(&t);  // ✅ Tigre como Felino
}

Array polimórfico:

// Almacenar diferentes tipos en un array de punteros a la clase base
Felino* felinos[4];
felinos[0] = new Gato();
felinos[1] = new Leon();
felinos[2] = new Tigre();
felinos[3] = new Gato();

// Iterar sobre todos, sin importar su tipo real
for (int i = 0; i < 4; i++) {
    felinos[i]->hacer_sonido();  // Cada uno hace su sonido
}

Output:

Miau!
Roar!
Grrr!
Miau!

Tipos de polimorfismo:

Tipo Cuándo se resuelve Ejemplo
Estático Compilación Sobrecarga de funciones
Dinámico Ejecución Funciones virtuales

Ejemplo de polimorfismo estático (sobrecarga):

class Calculadora {
public:
    int sumar(int a, int b) {
        return a + b;
    }

    double sumar(double a, double b) {  // Mismo nombre, diferente tipo
        return a + b;
    }

    int sumar(int a, int b, int c) {    // Mismo nombre, diferente cantidad
        return a + b + c;
    }
};

Calculadora calc;
calc.sumar(2, 3);        // Llama a sumar(int, int)
calc.sumar(2.5, 3.7);    // Llama a sumar(double, double)
calc.sumar(1, 2, 3);     // Llama a sumar(int, int, int)

Ventajas del polimorfismo: - 🔄 Flexibilidad: Mismo código funciona con diferentes tipos - 📦 Extensibilidad: Fácil agregar nuevos tipos - 🎯 Abstracción: Trabajar con conceptos genéricos - 🧩 Código limpio: Menos condicionales (if/switch)

Comparación Final: Estructural vs POO

Programa Estructural

// Datos
struct Cuenta {
    double saldo;
    char titular[50];
};

// Funciones separadas
void depositar(struct Cuenta* c, double monto) {
    c->saldo += monto;
}

double consultar_saldo(struct Cuenta* c) {
    return c->saldo;
}

// Uso
struct Cuenta c1;
depositar(&c1, 1000);
double s = consultar_saldo(&c1);

Problemas: - ❌ Datos expuestos: cualquiera puede hacer c1.saldo = -9999 - ❌ Funciones dispersas: no hay agrupación lógica - ❌ Sin protección: no hay encapsulación

Programa POO

// Clase: datos + funciones juntos
class Cuenta {
    double saldo;      // Privado
    string titular;    // Privado

public:
    void depositar(double monto) {
        if (monto > 0) {
            saldo += monto;
        }
    }

    double consultar_saldo() {
        return saldo;
    }
};

// Uso
Cuenta c1;
c1.depositar(1000);
double s = c1.consultar_saldo();
// c1.saldo = -9999;  // ❌ ERROR: protegido

Ventajas: - ✅ Datos protegidos: solo acceso controlado - ✅ Funciones agrupadas: todo relacionado junto - ✅ Encapsulación: implementación oculta

Resumen de Conceptos

Concepto Definición Breve Analogía
POO Paradigma que agrupa datos y funciones Organizar por objetos del mundo real
Objeto Instancia de una clase con estado y comportamiento Un coche específico (no el plano)
Clase Plantilla para crear objetos Plano de una casa
Método Función de un objeto Botón de un control remoto
Mensaje Llamada a un método Presionar un botón
Interfaz Parte pública de una clase Controles visibles de un aparato
Herencia Crear clases basadas en otras Hijo hereda características de padres
Jerarquía Estructura de árbol de clases Árbol genealógico
Polimorfismo Mismo objeto, múltiples formas Actor que interpreta varios roles

Pilares de la POO

1. Encapsulación 🔒

class Ejemplo {
private:    // Oculto
    int dato;
public:     // Visible
    void set_dato(int d) { dato = d; }
    int get_dato() { return dato; }
};

2. Herencia 🧬

class Base {
    // ...
};

class Derivada : public Base {
    // Hereda de Base + añade nuevas características
};

3. Polimorfismo 🎭

Base* ptr = new Derivada();
ptr->metodo();  // Ejecuta versión de Derivada

4. Abstracción 🎨

class Forma {  // Concepto abstracto
public:
    virtual double area() = 0;  // Método puro virtual
};

Referencias

  • Programación Orientada a Objetos (Grady Booch)
  • The C++ Programming Language (Bjarne Stroustrup)
  • Effective C++ (Scott Meyers)
  • Object-Oriented Software Construction (Bertrand Meyer)