Menu
Coddy logo textTech

インターフェースの設計

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 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 コメントが含まれています。これらのコメントに従って、適切な継承とインターフェースの実装を備えた完全なメディアプレーヤーシステムを作成してください。

チートシート

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

インターフェースをポリモーフィックに使用します:

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

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

インターフェースを型ヒントとして使用します:

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

インターフェースは、クラスが「どのように行うか」ではなく「何をすべきか」を定義します。実装クラスに対して明確な規約(コントラクト)を作成するために、インターフェース内のすべてのメソッドは抽象メソッドである必要があります。

自分で試してみよう

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腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン