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
- Encapsulamiento: ocultar los detalles internos, exponer solo lo necesario
- Herencia: crear clases nuevas basadas en clases existentes
- Polimorfismo: un mismo método se comporta diferente según el objeto
- 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):
Diálogo para agregar una nueva clase al proyecto
💡 Consejo: Cada clase debería ir en su propio archivo
.cscon el mismo nombre que la clase. Por ejemplo, la clasePersonava en el archivoPersona.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
staticpertenece 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 |