Menu
Coddy logo textTech

Interface-Design

Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 26 von 64.

Ein Interface definiert einen Vertrag, dem Klassen folgen müssen. In Python erstellen wir Interfaces mithilfe von abstrakten Basisklassen, bei denen alle Methoden abstrakt sind.

Importieren Sie das abc-Modul:

from abc import ABC, abstractmethod

Erstellen Sie ein Interface mit ausschließlich abstrakten Methoden:

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

Alle Methoden in einem Interface sollten abstrakt sein – sie definieren, was implementierende Klassen tun müssen, nicht wie sie es tun sollen.

Implementieren Sie das Interface in einer konkreten Klasse:

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}"

Erstellen Sie eine weitere Klasse, die dieselbe Schnittstelle implementiert:

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}"

Verwenden Sie das Interface polymorph:

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

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

Ausgabe:

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

Sie können Interfaces auch als Type-Hints verwenden:

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

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

Ausgabe:

Drawing a circle

Kernpunkt: Interfaces definieren, was Klassen tun müssen, nicht wie sie es tun. Verwenden Sie abstrakte Basisklassen mit ausschließlich abstrakten Methoden, um klare Verträge zu erstellen, denen implementierende Klassen folgen müssen. Dies gewährleistet ein konsistentes Verhalten über verschiedene Implementierungen hinweg.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementieren Sie ein Mediaplayer-System mit Schnittstellen.

Sie müssen die folgenden Dateien bearbeiten:

  • playable.py – Implementieren Sie die abstrakten Basisklassen (Schnittstellen)
  • song.py – Implementieren Sie die Klasse Song
  • video.py – Implementieren Sie die Klasse Video
  • mediaplayer.py – Implementieren Sie die Klasse MediaPlayer

Jede Datei enthält detaillierte TODO-Kommentare, die Sie bei Ihrer Implementierung unterstützen. Folgen Sie diesen Kommentaren, um ein vollständiges Mediaplayer-System mit ordnungsgemäßer Vererbung und Schnittstellenimplementierung zu erstellen.

Spickzettel

Erstellen Sie Schnittstellen mit abstrakten Basisklassen unter Verwendung des abc-Moduls:

from abc import ABC, abstractmethod

Definieren Sie eine Schnittstelle mit ausschließlich abstrakten Methoden:

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

Implementieren Sie die Schnittstelle in konkreten Klassen:

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}"

Verwenden Sie Schnittstellen polymorph:

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

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

Verwenden Sie Schnittstellen als Typ-Hinweise (Type Hints):

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

Schnittstellen definieren, was Klassen tun müssen, nicht wie sie es tun. Alle Methoden in einer Schnittstelle sollten abstrakt sein, um klare Verträge für implementierende Klassen zu erstellen.

Probier es selbst

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

# Umfassender Testfall-Handler
test_case = input()

if test_case == "default_test":
    # Standard-Testfall aus der ursprünglichen Problemstellung
    song = Song("Bohemian Rhapsody", "Queen", 355)
    video = Video("Python Tutorial", "1080p", 1800)
    player = MediaPlayer()

    # Test mit Song
    player.set_media(song)
    print(player.current_media.get_info())
    print(player.play())
    print(player.pause())
    print(player.stop())
    print()  # Leerzeile für bessere Lesbarkeit

    # Test mit 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":
    # Testet den Media Player ohne gesetztes Medium
    player = MediaPlayer()
    print(player.play())  # Sollte "No media set" ausgeben
    print(player.pause())  # Sollte "No media set" ausgeben
    print(player.stop())  # Sollte "No media set" ausgeben

elif test_case == "time_formatting":
    # Testet die Zeitformatierung in der get_info() Methode
    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":
    # Testet, ob Song und Video die Interfaces korrekt implementieren
    song = Song("Test Song", "Test Artist", 180)
    video = Video("Test Video", "480p", 240)
    
    # Überprüfung der Interface-Implementierung
    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)}")
    
    # Testet alle Interface-Methoden
    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":
    # Testet polymorphes Verhalten mit einer Liste verschiedener Medientypen
    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":
    # Testet Grenzfälle
    empty_song = Song("", "", 0)
    edge_video = Video("A" * 100, "", -10)  # Sehr langer Titel, leere Auflösung, negative Dauer
    
    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":
    # Implementiert einen Belastungstest mit vielen Medienobjekten
    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()
    
    # Test mit allen Songs
    for i, song in enumerate(songs):
        player.set_media(song)
        if i % 10 == 0:  # Nur jeden 10. ausgeben, um zu viel Output zu vermeiden
            print(f"Playing song {i+1}: {player.play()}")
    
    # Test mit allen Videos
    for i, video in enumerate(videos):
        player.set_media(video)
        if i % 10 == 0:  # Nur jeden 10. ausgeben, um zu viel Output zu vermeiden
            print(f"Playing video {i+1}: {player.play()}")
    
    print("Stress test completed successfully!")
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Object Oriented Programming