Interfaces y Abstract Base Class (ABC)

Interfaces informales


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.

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.