Introducción
Contenido de la lección.
Objetivos de aprendizaje
- Objetivo 1
- Objetivo 2
- Objetivo 3
Objetivos de Aprendizaje
Al finalizar este tema, serás capaz de:
- Implementar encapsulación usando propiedades, getters y setters en Python
- Aplicar convenciones de nomenclatura para controlar el acceso a atributos
- Crear y utilizar clases abstractas con el módulo abc
- Diseñar interfaces robustas y mantenibles para aplicaciones profesionales
📚 Fundamentos de Encapsulación en Python
La encapsulación es el principio de ocultar los detalles internos de implementación y exponer solo las interfaces necesarias. En Python, aunque no existen modificadores de acceso explícitos como en otros lenguajes, se utilizan convenciones de nomenclatura. Los atributos que comienzan con un guión bajo (_) se consideran protegidos, y los que comienzan con doble guión bajo (__) se consideran privados. Python implementa 'name mangling' para los atributos privados, modificando internamente su nombre para dificultar el acceso directo. Esta aproximación permite flexibilidad manteniendo buenas prácticas de diseño.
class CuentaBancaria:
def __init__(self, titular, saldo_inicial):
self.titular = titular # Público
self._numero_cuenta = '12345' # Protegido
self.__saldo = saldo_inicial # Privado
def mostrar_info(self):
return f'Titular: {self.titular}, Saldo: {self.__saldo}'
# Uso de la clase
cuenta = CuentaBancaria('Juan Pérez', 1000)
print(cuenta.titular) # Acceso público
print(cuenta._numero_cuenta) # Acceso protegido (no recomendado)
# print(cuenta.__saldo) # Error: no existe
print(cuenta.mostrar_info())
Juan Pérez 12345 Titular: Juan Pérez, Saldo: 1000
🎯 Propiedades con @property: Getters y Setters
El decorador @property permite crear atributos computados y controlar el acceso de lectura y escritura a los datos. Los getters se definen con @property, los setters con @atributo.setter, y los deleters con @atributo.deleter. Este mecanismo proporciona una interfaz limpia para validar datos, realizar cálculos automáticos y mantener la integridad de los objetos. Las propiedades permiten cambiar la implementación interna sin afectar el código cliente, siguiendo el principio de encapsulación.
class Temperatura:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, valor):
if valor < -273.15:
raise ValueError('Temperatura no puede ser menor al cero absoluto')
self._celsius = valor
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, valor):
self.celsius = (valor - 32) * 5/9
# Uso de las propiedades
temp = Temperatura(25)
print(f'Celsius: {temp.celsius}')
print(f'Fahrenheit: {temp.fahrenheit}')
temp.fahrenheit = 100
print(f'Nuevo celsius: {temp.celsius}')
Celsius: 25 Fahrheit: 77.0 Nuevo celsius: 37.77777777777778
class Empleado:
def __init__(self, nombre, salario):
self._nombre = nombre
self._salario = salario
self._antiguedad = 0
@property
def nombre(self):
return self._nombre
@property
def salario(self):
return self._salario
@salario.setter
def salario(self, nuevo_salario):
if nuevo_salario < 0:
raise ValueError('El salario no puede ser negativo')
if nuevo_salario < self._salario * 0.8:
raise ValueError('Reducción de salario muy grande')
self._salario = nuevo_salario
@property
def salario_anual(self):
return self._salario * 12
def aumentar_antiguedad(self):
self._antiguedad += 1
# Demostración
emp = Empleado('Ana García', 3000)
print(f'Salario mensual: {emp.salario}')
print(f'Salario anual: {emp.salario_anual}')
emp.salario = 3500
print(f'Nuevo salario anual: {emp.salario_anual}')
Salario mensual: 3000 Salario anual: 36000 Nuevo salario anual: 42000
💡 Clases Abstractas y el Módulo ABC
Las clases abstractas definen una interfaz común que debe ser implementada por las subclases. Python utiliza el módulo abc (Abstract Base Classes) para crear clases abstractas mediante el decorador @abstractmethod. Una clase abstracta no puede ser instanciada directamente y requiere que las subclases implementen todos los métodos abstractos. Esto garantiza que las clases derivadas cumplan con un contrato específico, facilitando el polimorfismo y el diseño de APIs consistentes. Las clases abstractas pueden combinar métodos abstractos con implementaciones concretas.
from abc import ABC, abstractmethod
import math
class Forma(ABC):
def __init__(self, nombre):
self._nombre = nombre
@property
def nombre(self):
return self._nombre
@abstractmethod
def area(self):
pass
@abstractmethod
def perimetro(self):
pass
def describir(self):
return f'{self.nombre}: Área={self.area():.2f}, Perímetro={self.perimetro():.2f}'
class Circulo(Forma):
def __init__(self, radio):
super().__init__('Círculo')
self._radio = radio
def area(self):
return math.pi * self._radio ** 2
def perimetro(self):
return 2 * math.pi * self._radio
class Rectangulo(Forma):
def __init__(self, ancho, alto):
super().__init__('Rectángulo')
self._ancho = ancho
self._alto = alto
def area(self):
return self._ancho * self._alto
def perimetro(self):
return 2 * (self._ancho + self._alto)
# Uso de las clases
circulo = Circulo(5)
rectangulo = Rectangulo(4, 6)
print(circulo.describir())
print(rectangulo.describir())
Círculo: Área=78.54, Perímetro=31.42 Rectángulo: Área=24.00, Perímetro=20.00
🔧 Casos de Uso Avanzados: Sistema de Gestión
En aplicaciones profesionales, la encapsulación y abstracción se combinan para crear sistemas robustos y escalables. Un ejemplo común es un sistema de gestión donde diferentes tipos de entidades comparten comportamientos comunes pero tienen implementaciones específicas. La combinación de clases abstractas, propiedades validadas y encapsulación apropiada permite crear código mantenible y extensible. Este enfoque facilita la incorporación de nuevas funcionalidades sin modificar el código existente, siguiendo principios SOLID.
from abc import ABC, abstractmethod
class Vehiculo(ABC):
def __init__(self, marca, modelo, año):
self._marca = marca
self._modelo = modelo
self._año = año
self._kilometraje = 0
self._activo = True
@property
def info_basica(self):
return f'{self._marca} {self._modelo} ({self._año})'
@property
def kilometraje(self):
return self._kilometraje
@kilometraje.setter
def kilometraje(self, km):
if km < self._kilometraje:
raise ValueError('El kilometraje no puede disminuir')
self._kilometraje = km
@abstractmethod
def costo_mantenimiento(self):
pass
@abstractmethod
def tipo_combustible(self):
pass
class Auto(Vehiculo):
def __init__(self, marca, modelo, año, puertas):
super().__init__(marca, modelo, año)
self._puertas = puertas
def costo_mantenimiento(self):
return 500 + (self.kilometraje * 0.1)
def tipo_combustible(self):
return 'Gasolina'
class Camion(Vehiculo):
def __init__(self, marca, modelo, año, capacidad_carga):
super().__init__(marca, modelo, año)
self._capacidad_carga = capacidad_carga
def costo_mantenimiento(self):
return 1200 + (self.kilometraje * 0.2)
def tipo_combustible(self):
return 'Diésel'
# Demostración del sistema
vehiculos = [
Auto('Toyota', 'Corolla', 2020, 4),
Camion('Volvo', 'FH16', 2019, 25000)
]
for vehiculo in vehiculos:
vehiculo.kilometraje = 15000
print(f'{vehiculo.info_basica}')
print(f'Combustible: {vehiculo.tipo_combustible()}')
print(f'Costo mantenimiento: ${vehiculo.costo_mantenimiento():.2f}')
print('---')
Toyota Corolla (2020) Combustible: Gasolina Costo mantenimiento: $2000.00 --- Volvo FH16 (2019) Combustible: Diésel Costo mantenimiento: $4200.00 ---
Ejercicios Prácticos
Sistema de Cuentas Bancarias Encapsulado
INTERMEDIODescripción:
Crear una clase CuentaBancaria que implemente encapsulación completa con propiedades para saldo, número de cuenta y titular. Incluir validaciones para transferencias y retiros, y métodos para consultar historial de transacciones. El saldo debe ser privado y solo modificable a través de métodos controlados.
Jerarquía de Empleados con Clases Abstractas
AVANZADODescripción:
Diseñar un sistema de empleados con clase abstracta Empleado que defina métodos para calcular salario y beneficios. Crear subclases EmpleadoTiempoCompleto, EmpleadoMedioTiempo y Contratista, cada una con sus propias reglas de cálculo. Implementar propiedades validadas para datos personales.
Sistema de Inventario con Abstracción
AVANZADODescripción:
Crear un sistema de inventario donde Producto sea una clase abstracta con métodos para calcular precio final, obtener información y validar stock. Implementar ProductoFisico y ProductoDigital con comportamientos específicos. Incluir propiedades para precio, descuentos y disponibilidad.
Validador de Datos con Propiedades
BÁSICODescripción:
Implementar una clase Persona que utilice propiedades para validar email, teléfono, edad y nombre. Cada propiedad debe tener validaciones específicas y proporcionar mensajes de error descriptivos. Incluir propiedades de solo lectura para datos calculados como edad en días.
Resumen y Próximos Pasos
La encapsulación y abstracción son herramientas poderosas que permiten crear código Python más robusto y mantenible. Hemos aprendido a controlar el acceso a los datos mediante propiedades, implementar validaciones efectivas con getters y setters, y diseñar interfaces consistentes usando clases abstractas. Estos conceptos son fundamentales para el desarrollo de aplicaciones profesionales y sistemas escalables. En el próximo tema exploraremos patrones de diseño que aprovechan estos principios para resolver problemas comunes de arquitectura de software, incluyendo Factory, Observer y Strategy patterns.