Módulo 11: Programación Orientada a Objetos — Fundamentos

Objetivos del módulo

  • Comprender qué es la Programación Orientada a Objetos (POO)
  • Crear clases con propiedades, campos y métodos
  • Entender constructores y la inicialización de objetos
  • Dominar el concepto de encapsulamiento
  • Aprender la diferencia entre tipos valor y referencia

1. ¿Qué es la POO?

📘 Concepto: La Programación Orientada a Objetos es un paradigma que organiza el código alrededor de objetos: entidades que combinan datos (propiedades) y comportamiento (métodos). Es como modelar el mundo real en código.

Analogía

Mundo Real POO
Plano de una casa Clase (la plantilla)
Casa construida Objeto (instancia de la clase)
Nº de habitaciones, color Propiedades (datos)
Abrir puerta, encender calefacción Métodos (acciones)

Los 4 pilares de la POO

  1. Encapsulamiento: ocultar los detalles internos, exponer solo lo necesario
  2. Herencia: crear clases nuevas basadas en clases existentes
  3. Polimorfismo: un mismo método se comporta diferente según el objeto
  4. Abstracción: simplificar la complejidad mostrando solo lo esencial

2. Clases y objetos

Crear una clase en Visual Studio Community

Para añadir una nueva clase a tu proyecto, haz clic derecho en el proyecto dentro del Explorador de soluciones y selecciona Agregar → Clase… (o usa el atajo Shift+Alt+C):

Agregar una nueva clase en Visual Studio Diálogo para agregar una nueva clase al proyecto

💡 Consejo: Cada clase debería ir en su propio archivo .cs con el mismo nombre que la clase. Por ejemplo, la clase Persona va en el archivo Persona.cs.

Crear una clase

class Persona
{
    // Propiedades: datos que describe a la persona
    public string Nombre { get; set; } = "";
    public int Edad { get; set; }
    public string Email { get; set; } = "";

    // Método: acción que la persona puede hacer
    public void Presentarse()
    {
        Console.WriteLine($"Hola, soy {Nombre}, tengo {Edad} años.");
    }
}
Parte Significado
class Persona Define una nueva clase llamada Persona
public Accesible desde cualquier parte del programa
string Nombre { get; set; } Propiedad auto-implementada (lectura y escritura)
= "" Valor inicial por defecto
void Presentarse() Método sin retorno

Crear objetos (instanciar)

// Crear un objeto de tipo Persona
Persona persona1 = new Persona();
persona1.Nombre = "Ana";
persona1.Edad = 25;
persona1.Email = "ana@email.com";
persona1.Presentarse();  // Hola, soy Ana, tengo 25 años.

// Sintaxis más corta con inicializador de objetos
Persona persona2 = new()
{
    Nombre = "Luis",
    Edad = 30,
    Email = "luis@email.com"
};
persona2.Presentarse();  // Hola, soy Luis, tengo 30 años.

// También se puede usar 'var'
var persona3 = new Persona { Nombre = "María", Edad = 22 };

3. Constructores

📘 Concepto: Un constructor es un método especial que se ejecuta automáticamente al crear un objeto (new). Se usa para inicializar el objeto con datos obligatorios.

class Producto
{
    public string Nombre { get; set; }
    public double Precio { get; set; }
    public int Stock { get; set; }

    // Constructor: mismo nombre que la clase, sin tipo de retorno
    public Producto(string nombre, double precio, int stock)
    {
        Nombre = nombre;
        Precio = precio;
        Stock = stock;
    }

    public void MostrarInfo()
    {
        Console.WriteLine($"{Nombre}: {Precio:C2} ({Stock} uds)");
    }
}

// Uso: SIEMPRE hay que pasar los 3 parámetros
Producto p1 = new Producto("Teclado", 49.99, 100);
p1.MostrarInfo();  // Teclado: 49,99 € (100 uds)

// Producto p2 = new Producto();  // ERROR: no existe constructor sin parámetros

Constructor por defecto

Si no defines ningún constructor, C# crea uno vacío automáticamente. Pero si defines uno con parámetros, el vacío desaparece. Si necesitas ambos, los defines explícitamente:

class Coche
{
    public string Marca { get; set; } = "Sin marca";
    public string Modelo { get; set; } = "Sin modelo";
    public int Año { get; set; }

    // Constructor vacío (por defecto)
    public Coche() { }

    // Constructor con parámetros
    public Coche(string marca, string modelo, int año)
    {
        Marca = marca;
        Modelo = modelo;
        Año = año;
    }
}

// Ambos son válidos
Coche c1 = new Coche();
Coche c2 = new Coche("Toyota", "Corolla", 2024);

Constructor primario (C# 12+)

C# moderno permite definir constructores directamente en la declaración de la clase:

class Rectangulo(double ancho, double alto)
{
    public double Ancho { get; } = ancho;
    public double Alto { get; } = alto;
    public double Area() => Ancho * Alto;
    public double Perimetro() => 2 * (Ancho + Alto);
}

var rect = new Rectangulo(5, 3);
Console.WriteLine($"Área: {rect.Area()}");        // 15
Console.WriteLine($"Perímetro: {rect.Perimetro()}"); // 16

4. Propiedades en detalle

Propiedades auto-implementadas

class Alumno
{
    public string Nombre { get; set; } = "";  // Lectura y escritura
    public int Edad { get; set; }
}

Propiedades de solo lectura

class Circulo
{
    public double Radio { get; }  // Solo se puede asignar en el constructor

    public Circulo(double radio)
    {
        Radio = radio;
    }
}

var c = new Circulo(5);
// c.Radio = 10;  // ERROR: es de solo lectura

Propiedades con lógica (getter/setter personalizado)

class CuentaBancaria
{
    private double _saldo;  // Campo privado (convención: _camelCase)

    public double Saldo
    {
        get { return _saldo; }  // Al leer: devuelve el valor
        private set                // Al escribir: solo desde dentro de la clase
        {
            if (value < 0)
                throw new InvalidOperationException("El saldo no puede ser negativo");
            _saldo = value;
        }
    }

    public CuentaBancaria(double saldoInicial)
    {
        Saldo = saldoInicial;
    }

    public void Ingresar(double monto)
    {
        if (monto <= 0)
            throw new ArgumentException("El monto debe ser positivo");
        Saldo += monto;
    }

    public void Retirar(double monto)
    {
        if (monto <= 0)
            throw new ArgumentException("El monto debe ser positivo");
        if (monto > Saldo)
            throw new InvalidOperationException("Saldo insuficiente");
        Saldo -= monto;
    }
}

var cuenta = new CuentaBancaria(1000);
cuenta.Ingresar(500);
Console.WriteLine(cuenta.Saldo);  // 1500

// cuenta.Saldo = 999999;  // ERROR: set es private

Propiedades calculadas

class Rectangulo
{
    public double Ancho { get; set; }
    public double Alto { get; set; }

    // Propiedad calculada: no almacena valor, lo calcula cada vez
    public double Area => Ancho * Alto;
    public double Perimetro => 2 * (Ancho + Alto);
    public bool EsCuadrado => Ancho == Alto;
}

var r = new Rectangulo { Ancho = 4, Alto = 6 };
Console.WriteLine($"Área: {r.Area}");          // 24
Console.WriteLine($"¿Cuadrado? {r.EsCuadrado}"); // False

5. Encapsulamiento y modificadores de acceso

📘 Concepto: El encapsulamiento consiste en ocultar los detalles internos de una clase y exponer solo lo que el usuario de la clase necesita. Se logra con modificadores de acceso.

Modificador Acceso
public Desde cualquier parte
private Solo dentro de la misma clase
protected Dentro de la clase y sus hijas (herencia)
internal Dentro del mismo proyecto (assembly)
private protected Clase + hijas, pero solo en el mismo proyecto
class Motor
{
    private int _rpm;                // Solo accesible dentro de Motor
    private double _temperatura;     // Solo accesible dentro de Motor

    public bool EnMarcha { get; private set; }  // Leer desde fuera, escribir solo dentro

    public void Arrancar()
    {
        _rpm = 800;
        _temperatura = 20;
        EnMarcha = true;
        Console.WriteLine("Motor arrancado");
    }

    public void Acelerar(int incremento)
    {
        if (!EnMarcha)
            throw new InvalidOperationException("El motor no está arrancado");

        _rpm += incremento;
        _temperatura += incremento * 0.1;
        Console.WriteLine($"RPM: {_rpm}, Temp: {_temperatura:F1}°C");
    }

    public void Apagar()
    {
        _rpm = 0;
        EnMarcha = false;
        Console.WriteLine("Motor apagado");
    }
}

var motor = new Motor();
motor.Arrancar();       // Motor arrancado
motor.Acelerar(1000);   // RPM: 1800, Temp: 120.0°C
// motor._rpm = 5000;   // ERROR: _rpm es private

6. La palabra clave this

this hace referencia al objeto actual. Se usa para desambiguar cuando un parámetro tiene el mismo nombre que una propiedad:

class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; set; }

    public Persona(string nombre, int edad)
    {
        this.Nombre = nombre;  // this.Nombre = propiedad, nombre = parámetro
        this.Edad = edad;
    }

    // this también se usa para llamar a otro constructor
    public Persona(string nombre) : this(nombre, 0) { }
}

7. Tipos valor vs tipos referencia

📘 Concepto: En C#, los tipos se dividen en tipos valor (se almacena directamente el dato) y tipos referencia (se almacena una referencia a la ubicación en memoria).

Tipos Valor Tipos Referencia
int, double, bool, char string, class, array
struct, enum object, interface
Se guardan en el stack Se guardan en el heap
Se copia al asignar Se copia la referencia
// Tipo valor: se copia el dato
int a = 10;
int b = a;   // b es una COPIA independiente
b = 99;
Console.WriteLine(a);  // 10 (no cambió)

// Tipo referencia: se copia la referencia
Persona p1 = new Persona("Ana", 25);
Persona p2 = p1;  // p2 apunta al MISMO objeto que p1
p2.Edad = 99;
Console.WriteLine(p1.Edad);  // 99 (¡cambió porque apunta al mismo objeto!)
graph LR
    subgraph "Tipo Valor"
        A["a = 10"] --> VA["10"]
        B["b = a"] --> VB["10 (copia)"]
    end
    subgraph "Tipo Referencia"
        P1["p1"] --> OBJ["Persona<br/>Ana, 25"]
        P2["p2 = p1"] --> OBJ
    end

8. Miembros estáticos (static)

📘 Concepto: Un miembro static pertenece a la clase, no a un objeto individual. Se accede con el nombre de la clase, no con una instancia.

class Contador
{
    // Campo estático: compartido entre TODOS los objetos
    private static int _totalCreados = 0;

    public int Id { get; }
    public string Nombre { get; set; }

    public Contador(string nombre)
    {
        _totalCreados++;
        Id = _totalCreados;
        Nombre = nombre;
    }

    // Método estático: se llama con la clase, no con un objeto
    public static int ObtenerTotal() => _totalCreados;

    public void MostrarInfo()
    {
        Console.WriteLine($"[{Id}] {Nombre}");
    }
}

var c1 = new Contador("Primero");
var c2 = new Contador("Segundo");
var c3 = new Contador("Tercero");

c1.MostrarInfo();  // [1] Primero
c2.MostrarInfo();  // [2] Segundo
c3.MostrarInfo();  // [3] Tercero

// Acceso estático (con la clase, no con un objeto)
Console.WriteLine($"Total creados: {Contador.ObtenerTotal()}");  // 3

9. ToString(): representación en texto

Toda clase puede sobrescribir ToString() para definir cómo se muestra al convertirla a texto:

class Libro
{
    public string Titulo { get; set; } = "";
    public string Autor { get; set; } = "";
    public int Paginas { get; set; }

    public override string ToString()
    {
        return $"\"{Titulo}\" de {Autor} ({Paginas} págs)";
    }
}

var libro = new Libro { Titulo = "Don Quijote", Autor = "Cervantes", Paginas = 1345 };
Console.WriteLine(libro);  // "Don Quijote" de Cervantes (1345 págs)
// Sin ToString(): mostraría "Libro" (nombre de la clase)

10. Ejercicios

Ejercicio 1: Clase Alumno

Crea una clase Alumno con: nombre, notas (array de double), método Media(), método NotaMaxima(), método Aprobado() (media >= 5). Crea varios alumnos y muestra sus datos.

Ejercicio 2: Cuenta bancaria completa

Crea una clase CuentaBancaria con: titular, IBAN, saldo (privado), historial de movimientos (lista). Métodos: Ingresar, Retirar, Transferir(CuentaBancaria destino, monto), MostrarHistorial. Valida todo.

Ejercicio 3: Biblioteca

Crea clases Libro y Biblioteca. La biblioteca tiene una lista de libros y métodos para: añadir, buscar por título/autor, prestar (marcar como no disponible), devolver. Sobrescribe ToString().

Ejercicio 4: Sistema de inventario

Crea una clase Producto con: ID (autoincremental con static), nombre, precio, stock. Métodos: Vender(cantidad), Reponer(cantidad). La clase debe tener un método estático ProductosConStockBajo() (stock < 5). Guarda y carga los datos en JSON (usando lo aprendido en el módulo anterior).


Resumen

Concepto Ejemplo
Crear clase class Persona { }
Crear objeto var p = new Persona();
Propiedad auto public string Nombre { get; set; }
Solo lectura public int Id { get; }
Propiedad calculada public double Area => Ancho * Alto;
Constructor public Persona(string nombre) { }
Encapsulamiento private, public, protected
Miembro estático public static int Total;
ToString public override string ToString() => ...;
Tipo valor int, double, struct → se copian
Tipo referencia class, array, string → se comparte