Menu
CoddyTech

接口设计

本课属于 Coddy Python 学习旅程的“面向对象编程”单元。第 26 课,共 64 课。

接口定义了类必须遵循的契约。在 Python 中,我们使用抽象基类创建接口,其中所有方法都是抽象的。

导入 abc 模块:

from abc import ABC, abstractmethod

创建一个只包含抽象方法的接口:

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

接口中的所有方法都应该是抽象的,它们定义了实现类必须做什么,而不是如何做。

在具体类中实现该接口:

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

再创建一个实现相同接口的类:

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

以多态方式使用该接口:

shapes = [Circle(5), Rectangle(3, 4)]
for shape in shapes:
    print(shape.draw())
    print(shape.resize(10, 8))

输出:

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

你也可以将接口用作类型提示:

def render_shape(drawable: Drawable):
    return drawable.draw()
circle = Circle(3)
print(render_shape(circle))

输出:

Drawing a circle

重点:接口规定类必须做什么,而不规定它们如何实现。使用只包含抽象方法的抽象基类,可以创建明确的契约,供实现类遵循。这样可以确保不同实现的行为保持一致。

challenge icon

挑战

中等

在这个挑战中,你将使用接口实现一个媒体播放器系统。

你需要编辑以下文件:

  • playable.py:实现抽象基类(接口)
  • song.py:实现 Song 类
  • video.py:实现 Video 类
  • mediaplayer.py:实现 MediaPlayer 类

每个文件都包含详细的 TODO 注释,为你的实现提供指引。按照这些注释,使用正确的继承关系并实现接口,构建一个完整的媒体播放器系统。

亲手试试

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

# 综合测试用例处理程序
test_case = input()

if test_case == "default_test":
    # 原题中的默认测试用例
    song = Song("Bohemian Rhapsody", "Queen", 355)
    video = Video("Python Tutorial", "1080p", 1800)
    player = MediaPlayer()

    # 用歌曲测试
    player.set_media(song)
    print(player.current_media.get_info())
    print(player.play())
    print(player.pause())
    print(player.stop())
    print()  # 用空行提高可读性

    # 用视频测试
    player.set_media(video)
    print(player.current_media.get_info())
    print(player.play())
    print(player.pause())
    print(player.stop())

elif test_case == "empty_player":
    # 测试未设置媒体时的媒体播放器
    player = MediaPlayer()
    print(player.play())  # 应输出 "No media set"
    print(player.pause())  # 应输出 "No media set"
    print(player.stop())  # 应输出 "No media set"

elif test_case == "time_formatting":
    # 测试 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":
    # 测试 Song 和 Video 是否正确实现接口
    song = Song("Test Song", "Test Artist", 180)
    video = Video("Test Video", "480p", 240)
    
    # 检查接口的实现情况
    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)}")
    
    # 测试所有接口方法
    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":
    # 用包含不同媒体类型的列表测试多态行为
    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":
    # 测试边界情况
    empty_song = Song("", "", 0)
    edge_video = Video("A" * 100, "", -10)  # 标题很长,分辨率为空,时长为负数
    
    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":
    # 用大量媒体对象进行压力测试
    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()
    
    # 测试所有歌曲
    for i, song in enumerate(songs):
        player.set_media(song)
        if i % 10 == 0:  # 每隔 10 个才打印一次,避免输出过多
            print(f"Playing song {i+1}: {player.play()}")
    
    # 测试所有视频
    for i, video in enumerate(videos):
        player.set_media(video)
        if i % 10 == 0:  # 每隔 10 个才打印一次,避免输出过多
            print(f"Playing video {i+1}: {player.play()}")
    
    print("Stress test completed successfully!")
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Python 在线编译器