Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

تصميم الواجهات

جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 26 من 64.

تحدد الواجهة (interface) عقداً يجب على الأصناف (classes) اتباعه. في لغة Python، نقوم بإنشاء الواجهات باستخدام الأصناف الأساسية المجردة (abstract base classes) حيث تكون جميع الدوال (methods) مجردة.

استورد وحدة abc:

from abc import ABC, abstractmethod

قم بإنشاء واجهة تحتوي على دوال مجردة (abstract methods) فقط:

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

يجب أن تكون جميع الدوال (methods) في الواجهة (interface) مجردة (abstract) - فهي تحدد ما يجب على الفئات (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}"

أنشئ فئة أخرى تُنفذ نفس الواجهة:

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

نقطة رئيسية: تحدد الواجهات (Interfaces) ما يجب على الفئات (classes) القيام به، وليس كيفية القيام به. استخدم الفئات الأساسية المجردة (abstract base classes) التي تحتوي فقط على أساليب مجردة (abstract methods) لإنشاء عقود واضحة يجب على الفئات المنفذة اتباعها. يضمن ذلك سلوكاً متسقاً عبر التنفيذات المختلفة.

challenge icon

التحدي

متوسط

في هذا التحدي، ستقوم بتنفيذ نظام مشغل وسائط باستخدام الواجهات (interfaces).

تحتاج إلى تعديل الملفات التالية:

  • playable.py - قم بتنفيذ الفئات الأساسية المجردة (abstract base classes) (الواجهات)
  • song.py - قم بتنفيذ فئة Song
  • video.py - قم بتنفيذ فئة Video
  • mediaplayer.py - قم بتنفيذ فئة MediaPlayer

يحتوي كل ملف على تعليقات TODO مفصلة لتوجيه عملية التنفيذ الخاصة بك. اتبع هذه التعليقات لإنشاء نظام مشغل وسائط كامل مع الوراثة الصحيحة وتنفيذ الواجهات.

ورقة مرجعية

قم بإنشاء واجهات (interfaces) باستخدام الفئات الأساسية المجردة (abstract base classes) مع وحدة abc:

from abc import ABC, abstractmethod

قم بتعريف واجهة تحتوي على دوال مجردة (abstract methods) فقط:

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

استخدم الواجهات بشكل متعدد الأشكال (polymorphically):

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

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

استخدم الواجهات كتلميحات للأنواع (type hints):

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:  # طباعة كل عاشر عنصر فقط لتجنب المخرجات الكثيرة جداً
            print(f"Playing song {i+1}: {player.play()}")
    
    # اختبار مع جميع الفيديوهات
    for i, video in enumerate(videos):
        player.set_media(video)
        if i % 10 == 0:  # طباعة كل عاشر عنصر فقط لتجنب المخرجات الكثيرة جداً
            print(f"Playing video {i+1}: {player.play()}")
    
    print("Stress test completed successfully!")
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming