Clases Abstractas y Protocolos
En el diseño de software orientado a objetos en Python existen dos formas principales de definir contratos e interfaces:
- Subtipado Nominal (Herencia Explícita): Mediante Clases Base Abstractas (
abc.ABC). - Subtipado Estructural (Duck Typing Estático): Mediante Protocolos (
typing.Protocol, PEP 544).
1. Clases Base Abstractas con abc.ABC #
Una Clase Base Abstracta (ABC) es una clase que no puede instanciarse directamente y sirve como plantilla para obligar a las clases hijas a implementar ciertos métodos.
Se utiliza el módulo estándar abc con la clase ABC y el decorador @abstractmethod:
1from abc import ABC, abstractmethod 2 3class Notificador(ABC): 4 @abstractmethod 5 def enviar(self, destinatario: str, mensaje: str) -> bool: 6 """Obliga a implementar el envío del mensaje.""" 7 pass 8 9class NotificadorEmail(Notificador): 10 def enviar(self, destinatario: str, mensaje: str) -> bool: 11 print(f"Enviando Email a {destinatario}: {mensaje}") 12 return True 13 14class NotificadorSMS(Notificador): 15 def enviar(self, destinatario: str, mensaje: str) -> bool: 16 print(f"Enviando SMS a {destinatario}: {mensaje}") 17 return True
⚠️ Si una subclase no implementa todos los
@abstractmethod, Python impedirá crear instancias lanzando unTypeError: Can't instantiate abstract class ... with abstract method ....
2. Protocolos y Duck Typing Estructural (typing.Protocol) #
Python es célebre por su filosofía de Duck Typing ("Si camina como un pato y grazna como un pato, es un pato").
Con Protocol (PEP 544), podemos definir interfaces formales sin obligar a las clases a heredar explícitamente de ninguna clase base. El comprobador de tipos (mypy) validará que la clase posea los métodos requeridos por su estructura:
1from typing import Protocol 2 3# Definición de la interfaz estructural 4class Dibujable(Protocol): 5 def dibujar(self) -> None: ... 6 7# Clases independientes sin herencia común 8class Circulo: 9 def dibujar(self) -> None: 10 print("Dibujando un círculo ⭕") 11 12class Cuadrado: 13 def dibujar(self) -> None: 14 print("Dibujando un cuadrado ⏹️") 15 16# Función que consume cualquier objeto compatible con el protocolo 17def renderizar(figura: Dibujable) -> None: 18 figura.dibujar() 19 20renderizar(Circulo()) 21renderizar(Cuadrado())Salida de consola Dibujando un círculo ⭕ Dibujando un cuadrado ⏹️
3. Comparación: ABC vs Protocol #
| Criterio | abc.ABC (Nominal) | typing.Protocol (Estructural) |
|---|---|---|
| Herencia | Obliga a heredar explícitamente (class Hijo(ABC)). | No requiere herencia; cualquier clase con métodos coincidentes es compatible. |
| Verificación en Runtime | Impide instanciar clases incompletas en tiempo de ejecución. | Principalmente estática (aunque admite decorador @runtime_checkable). |
| Ideal para | Jerarquías de clases estrechamente ligadas en el mismo dominio o librería. | Integración con librerías de terceros o sistemas desacoplados (Duck Typing tipado). |
Resumen del tema
Conceptos clave #
- Clases Abstractas (
abc.ABC): definen contratos rígidos mediante@abstractmethodque impiden instanciar subclases incompletas en runtime. - Protocolos (
typing.Protocol): definen contratos estructurales (structural subtyping) sin acoplamiento por herencia directa. - Desacoplamiento:
Protocolpermite que clases creadas por terceros cumplan con interfaces de tu aplicación sin modificar su código fuente.
Qué debes recordar #
Usa abc.ABC cuando desees imponer jerarquías de herencia estrictas con validación en tiempo de ejecución, y typing.Protocol para contratos flexibles y desacoplados basados en Duck Typing tipado.