Menu
Coddy logo textTech

Diseño de interfaces

Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 26 de 64.

Una interfaz define un contrato que las clases deben seguir. En Python, creamos interfaces utilizando clases base abstractas donde todos los métodos son abstractos.

Importa el módulo abc:

from abc import ABC, abstractmethod

Crea una interfaz solo con métodos abstractos:

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass
    
    @abstractmethod
    def resize(self, width, height):
        pass

Todos los métodos en una interfaz deben ser abstractos: definen lo que las clases que las implementan deben hacer, no cómo hacerlo.

Implementa la interfaz en una clase concreta:

class Circle(Drawable):
    def __init__(self, radius):
        self.radius = radius
    
    def draw(self):
        return "Drawing a circle"
    
    def resize(self, width, height):
        self.radius = min(width, height) / 2
        return f"Resized circle to radius {self.radius}"

Crea otra clase que implemente la misma interfaz:

class Rectangle(Drawable):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def draw(self):
        return "Drawing a rectangle"
    
    def resize(self, width, height):
        self.width = width
        self.height = height
        return f"Resized rectangle to {width}x{height}"

Usa la interfaz de forma polimórfica:

shapes = [Circle(5), Rectangle(3, 4)]

for shape in shapes:
    print(shape.draw())
    print(shape.resize(10, 8))

Salida:

Drawing a circle
Resized circle to radius 4.0
Drawing a rectangle
Resized rectangle to 10x8

También puedes usar interfaces como sugerencias de tipo (type hints):

def render_shape(drawable: Drawable):
    return drawable.draw()

circle = Circle(3)
print(render_shape(circle))

Resultado:

Drawing a circle

Punto clave: Las interfaces definen qué deben hacer las clases, no cómo lo hacen. Utilice clases base abstractas con solo métodos abstractos para crear contratos claros que las clases implementadoras deban seguir. Esto garantiza un comportamiento consistente a través de diferentes implementaciones.

challenge icon

Desafío

Intermedio

En este desafío, implementarás un sistema de reproductor de medios utilizando interfaces.

Debes editar los siguientes archivos:

  • playable.py - Implementa las clases base abstractas (interfaces)
  • song.py - Implementa la clase Song
  • video.py - Implementa la clase Video
  • mediaplayer.py - Implementa la clase MediaPlayer

Cada archivo contiene comentarios TODO detallados para guiar tu implementación. Sigue estos comentarios para crear un sistema de reproductor de medios completo con la herencia y la implementación de interfaces adecuadas.

Hoja de referencia

Crea interfaces utilizando clases base abstractas con el módulo abc:

from abc import ABC, abstractmethod

Define una interfaz solo con métodos abstractos:

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass
    
    @abstractmethod
    def resize(self, width, height):
        pass

Implementa la interfaz en clases concretas:

class Circle(Drawable):
    def __init__(self, radius):
        self.radius = radius
    
    def draw(self):
        return "Drawing a circle"
    
    def resize(self, width, height):
        self.radius = min(width, height) / 2
        return f"Resized circle to radius {self.radius}"

Usa interfaces de forma polimórfica:

shapes = [Circle(5), Rectangle(3, 4)]

for shape in shapes:
    print(shape.draw())
    print(shape.resize(10, 8))

Usa interfaces como pistas de tipo (type hints):

def render_shape(drawable: Drawable):
    return drawable.draw()

Las interfaces definen qué deben hacer las clases, no cómo lo hacen. Todos los métodos en una interfaz deben ser abstractos para crear contratos claros para las clases que los implementan.

Pruébalo tú mismo

from song import Song
from video import Video
from mediaplayer import MediaPlayer
from playable import Playable, MediaInfo

# Manejador integral de casos de prueba
test_case = input()

if test_case == "default_test":
    # Caso de prueba por defecto del problema original
    song = Song("Bohemian Rhapsody", "Queen", 355)
    video = Video("Python Tutorial", "1080p", 1800)
    player = MediaPlayer()

    # Prueba con canción
    player.set_media(song)
    print(player.current_media.get_info())
    print(player.play())
    print(player.pause())
    print(player.stop())
    print()  # Línea vacía para legibilidad

    # Prueba con video
    player.set_media(video)
    print(player.current_media.get_info())
    print(player.play())
    print(player.pause())
    print(player.stop())

elif test_case == "empty_player":
    # Probar el reproductor de medios sin ningún medio configurado
    player = MediaPlayer()
    print(player.play())  # Debería imprimir "No media set"
    print(player.pause())  # Debería imprimir "No media set"
    print(player.stop())  # Debería imprimir "No media set"

elif test_case == "time_formatting":
    # Probar el formato de tiempo en el método get_info()
    song1 = Song("Short Song", "Artist A", 65)  # 1:05
    song2 = Song("Long Song", "Artist B", 3661)  # 61:01
    video1 = Video("Hour Video", "720p", 3600)  # 60:00
    
    print(song1.get_info())
    print(song2.get_info())
    print(video1.get_info())

elif test_case == "interface_compliance":
    # Probar que Song y Video implementan correctamente las interfaces
    song = Song("Test Song", "Test Artist", 180)
    video = Video("Test Video", "480p", 240)
    
    # Verificar implementación de interfaces
    print(f"Song implements Playable: {isinstance(song, Playable)}")
    print(f"Song implements MediaInfo: {isinstance(song, MediaInfo)}")
    print(f"Video implements Playable: {isinstance(video, Playable)}")
    print(f"Video implements MediaInfo: {isinstance(video, MediaInfo)}")
    
    # Probar todos los métodos de la interfaz
    print(song.play())
    print(song.pause())
    print(song.stop())
    print(song.get_title())
    print(song.get_duration())
    print(song.get_info())
    
    print(video.play())
    print(video.pause())
    print(video.stop())
    print(video.get_title())
    print(video.get_duration())
    print(video.get_info())

elif test_case == "polymorphism":
    # Probar el comportamiento polimórfico con una lista de diferentes tipos de medios
    media_list = [
        Song("Song 1", "Artist 1", 180),
        Video("Video 1", "720p", 300),
        Song("Song 2", "Artist 2", 240),
        Video("Video 2", "1080p", 420)
    ]
    
    player = MediaPlayer()
    
    for media in media_list:
        player.set_media(media)
        print(f"Media: {media.get_title()}")
        print(f"Info: {media.get_info()}")
        print(f"Play: {player.play()}")
        print()

elif test_case == "edge_cases":
    # Probar casos límite
    empty_song = Song("", "", 0)
    edge_video = Video("A" * 100, "", -10)  # Título muy largo, resolución vacía, duración negativa
    
    print(f"Empty song info: {empty_song.get_info()}")
    print(f"Edge video info: {edge_video.get_info()}")
    
    player = MediaPlayer()
    player.set_media(empty_song)
    print(player.play())
    player.set_media(edge_video)
    print(player.play())

elif test_case == "stress_test":
    # Implementar una prueba de esfuerzo con muchos objetos de medios
    songs = [Song(f"Song {i}", f"Artist {i}", i * 30) for i in range(1, 101)]
    videos = [Video(f"Video {i}", f"{i*10}p", i * 60) for i in range(1, 101)]
    
    player = MediaPlayer()
    
    # Probar con todas las canciones
    for i, song in enumerate(songs):
        player.set_media(song)
        if i % 10 == 0:  # Imprimir solo cada 10 para evitar demasiada salida
            print(f"Playing song {i+1}: {player.play()}")
    
    # Probar con todos los videos
    for i, video in enumerate(videos):
        player.set_media(video)
        if i % 10 == 0:  # Imprimir solo cada 10 para evitar demasiada salida
            print(f"Playing video {i+1}: {player.play()}")
    
    print("Stress test completed successfully!")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming