# programacion-orientada-a-objetos-en-python
Programación Orientada a Objetos en Python
Volver a la portada del cursoUso de la abstracción en diseños
Sección 4 · Encapsulación y Abstracción
Uso de la abstracción en diseños
En esta práctica proteges el estado de los productos de PyShop y diseñas la tienda alrededor de abstracciones: el carrito trabaja con «un producto» y con «un descuento» sin saber qué clase concreta recibe. Descarga el notebook adjunto y ejecútalo en orden; todo usa la biblioteca estándar (abc, dataclasses y typing).
1. Encapsulación con property
Python no tiene atributos privados. La convención _stock avisa que es de uso interno, y @property controla el acceso real:
@property
def precio(self):
return self._precio
@precio.setter
def precio(self, valor):
if valor <= 0:
raise PrecioInvalidoError(f"Precio inválido para {self.nombre}: {valor}")
self._precio = round(valor, 2)
@property
def stock(self): # sin setter: solo lectura
return self._stock
Como __init__ asigna self.precio = precio, la validación también corre al crear el objeto. PrecioInvalidoError hereda de ValueError, así que except ValueError la atrapa. Y taza.stock = 1000 lanza AttributeError: property 'stock' of 'ProductoFisico' object has no setter: el stock solo cambia con reponer() y retirar().
2. Name mangling
Un atributo self.__fondo dentro de la clase Caja se guarda como _Caja__fondo. No es seguridad, porque se puede leer con ese nombre. Sirve para que una subclase con su propio __fondo no pise el de la base: en el notebook, vars(caja) muestra {'_Caja__fondo': 500, '_CajaExpress__fondo': 50}.
3. Clases abstractas
Producto hereda de ABC y marca costo_envio con @abstractmethod. Mientras falte implementar un método abstracto, la clase no se puede instanciar:
try:
Producto("X", 10)
except TypeError as e:
print(e)
# Can't instantiate abstract class Producto without an
# implementation for abstract method 'costo_envio'
El error aparece al crear el objeto, no al definir la clase ni al llamar el método. ProductoFisico cobra 49 + 15 * peso_kg * cantidad y ProductoDigital cobra 0: dos tazas de 0.4 kg pagan 61.0 de envío y un teclado de 1.2 kg paga 67.0.
4. Polimorfismo con descuentos
Descuento es otra clase abstracta con un solo método, aplicar(subtotal). El carrito llama self.descuento.aplicar(...) sin preguntar el tipo:
def total(self):
rebaja = self.descuento.aplicar(self.subtotal()) if self.descuento else 0.0
return round(self.subtotal() - rebaja + self.envio(), 2)
El carrito de Ana (2 tazas, 1 teclado y 1 ebook) tiene subtotal 1496.0, envío 128.0 y total 1624.0. Con DescuentoPorcentaje(10) baja a 1474.4. Con DescuentoFijo(200, minimo=1500) se queda en 1624.0, porque 1496.0 no alcanza el mínimo. Agregar un descuento nuevo no obliga a tocar Carrito.
5. Protocol: abstracción sin herencia
Con ABC una clase cumple el contrato porque hereda de él. Con typing.Protocol basta con que tenga los métodos. CuponNavidad no hereda de nada, pero tiene aplicar, así que el carrito lo usa sin problema (total 1474.0 con un cupón de 150). Con @runtime_checkable, isinstance(cupon, Aplicable) da True, mientras que isinstance(cupon, Descuento) da False.
Trampas comunes
- Creer que
__atributoes privado. Solo cambia el nombre; la encapsulación real la da@property. - Validar solo en el setter y asignar
self._precioen__init__. Así te saltas la validación; asignaself.precio. - Esperar que
@runtime_checkablerevise firmas. Solo comprueba que el atributo exista: una clase conaplicar = "texto"también pasa elisinstance. - Llenar el carrito de
if isinstance(...)para cada tipo de descuento. Si cada clase implementaaplicar, el carrito no necesita saber cuál recibe.
Qué hacer con el notebook
Ejecuta las seis secciones y después resuelve los dos ejercicios. En el primero creas DescuentoPorPiezas sin tocar Carrito: recibe el carrito en su constructor y aplica un porcentaje si len(carrito) alcanza el mínimo. Con 4 piezas y 5 %, el descuento es 74.8 y el total 1549.2; con un mínimo de 5 piezas no aplica y el total sigue en 1624.0. En el segundo conviertes peso_kg en una propiedad validada. Escribe tu versión antes de leer la solución y verifica que pase los mismos assert.