Menu
Coddy logo textTech

인터페이스 설계

Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 26번째.

인터페이스는 클래스가 따라야 하는 계약을 정의합니다. 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

구체 클래스(concrete classes)에서 인터페이스를 구현합니다:

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()

인터페이스는 클래스가 어떻게 수행하는지가 아니라 무엇을 해야 하는지를 정의합니다. 인터페이스의 모든 메서드는 구현 클래스를 위한 명확한 규약(contract)을 만들기 위해 추상 메서드여야 합니다.

직접 해보기

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의 모든 레슨