En la programación orientada a objetos, una interfaz define el conjunto de métodos que debe tener un objeto para cumplir cierta funcionalidad dentro de un sistema. Es decir, establece un contrato de comportamiento, indicando qué debe hacer un objeto, sin preocuparse por cómo lo hace.
Ejemplo conceptual: el mando a distancia
Piensa en un mando a distancia de un televisor. Sin importar la marca (Samsung, LG, etc.), todos ofrecen un conjunto común de funcionalidades: encender el televisor, cambiar el canal, subir o bajar el volumen. Ese conjunto de funcionalidades es una interfaz. Aunque cada mando implementa estas acciones de forma distinta internamente, el "contrato" que cumplen es el mismo.
Interfaces en Python:
A diferencia de otros lenguajes como Java o C#, Python no posee una palabra clave específica como interface
. Sin embargo, ofrece dos formas de definir interfaces:
- Interfaces informales, basadas en convenciones y herencia de clases base.
- Interfaces formales, usando el módulo
abc
(Abstract Base Classes).
Dependiendo del tipo de proyecto, una u otra puede ser más adecuada. Veamos ambas con detalle.
Interfaces Informales:
Una interfaz informal puede definirse simplemente como una clase base sin implementación concreta. Siguiendo con el ejemplo del mando, podríamos escribir:
class Mando:
def encender(self):
pass
def cambiar_canal(self, canal):
pass
def subir_volumen(self):
pass
def bajar_volumen(self):
pass
Luego, otras clases pueden heredar de esta y proporcionar su propia implementación:
class MandoSamsung(Mando):
def encender(self):
print("Samsung: Encendiendo TV")
def cambiar_canal(self, canal):
print(f"Samsung: Cambiando al canal {canal}")
def subir_volumen(self):
print("Samsung: Subiendo volumen")
def bajar_volumen(self):
print("Samsung: Bajando volumen")
Y lo mismo para otras marcas:
class MandoLG(Mando):
def encender(self):
print("LG: Encendiendo TV")
def cambiar_canal(self, canal):
print(f"LG: Cambiando al canal {canal}")
def subir_volumen(self):
print("LG: Subiendo volumen")
def bajar_volumen(self):
print("LG: Bajando volumen")
Este enfoque es válido y funcional, pero presenta una limitación importante: no obliga a las clases hijas a implementar los métodos de la interfaz. Si se omite alguno, Python no dará error hasta que se intente ejecutar.
Recomendación: Para detectar errores más rápidamente, puedes usar raise NotImplementedError()
en lugar de pass
.
class Mando:
def encender(self):
raise NotImplementedError("Método no implementado.")
Este comportamiento es posible gracias al duck typing de Python: "si camina como un pato y grazna como un pato, es un pato". Es decir, lo que importa es que un objeto tenga los métodos esperados, no necesariamente de qué clase proviene.