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

نمط التركيب

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

يعامل النمط المركب (Composite Pattern) الكائنات الفردية ومجموعات الكائنات بشكل موحد. فهو ينشئ هياكل شجرية تشترك فيها العناصر الفردية ومجموعات العناصر في الواجهة نفسها.

إليك مكونات بسيطة لنظام ملفات:

class File:
    def __init__(self, name, size):
        self.name = name
        self.size = size
    
    def get_size(self):
        return self.size
    
    def display(self):
        return f"File: {self.name} ({self.size}KB)"

class Folder:
    def __init__(self, name):
        self.name = name
        self.children = []
    
    def add(self, item):
        self.children.append(item)
    
    def get_size(self):
        total = 0
        for child in self.children:
            total += child.get_size()
        return total
    
    def display(self):
        result = f"Folder: {self.name}"
        for child in self.children:
            result += f"\n  {child.display()}"
        return result

تمتلك كل من الملفات والمجلدات نفس الأساليب (get_size() و display())، لذا يمكن التعامل معهما بشكل موحد.

بناء هيكل نظام الملفات:

# إنشاء ملفات
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)
file3 = File("video.mp4", 200)

# إنشاء مجلدات
documents = Folder("Documents")
media = Folder("Media")
root = Folder("Root")

# بناء هيكل الشجرة
documents.add(file1)
media.add(file2)
media.add(file3)
root.add(documents)
root.add(media)

استخدم البنية المركبة (composite structure):

print(f"Root size: {root.get_size()}KB")
print(root.display())

أنشئ مثالاً آخر بنظام قائمة طعام:

class MenuItem:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def get_price(self):
        return self.price
    
    def show(self):
        return f"{self.name}: ${self.price}"

class Menu:
    def __init__(self, name):
        self.name = name
        self.items = []
    
    def add(self, item):
        self.items.append(item)
    
    def get_price(self):
        total = 0
        for item in self.items:
            total += item.get_price()
        return total
    
    def show(self):
        result = f"{self.name} Menu:"
        for item in self.items:
            result += f"\n  {item.show()}"
        return result

combo = Menu("Combo")
combo.add(MenuItem("Burger", 8))
combo.add(MenuItem("Fries", 3))
combo.add(MenuItem("Drink", 2))

print(f"Combo price: ${combo.get_price()}")
print(combo.show())

المخرجات:

Root size: 260KB
Folder: Root
  Folder: Documents
    File: document.txt (10KB)
  Folder: Media
    File: image.jpg (50KB)
    File: video.mp4 (200KB)
Combo price: $13
Combo Menu:
  Burger: $8
  Fries: $3
  Drink: $2

نقطة رئيسية: يتيح لك نمط التركيب (Composite Pattern) التعامل مع الكائنات الفردية ومجموعات الكائنات بنفس الطريقة. حيث تقوم كل من الأوراق (العناصر الفردية) والتركيبات (المجموعات) بتنفيذ الواجهة البرمجية (interface) نفسها، مما يسهل العمل مع الهياكل الشجرية مثل أنظمة الملفات، أو القوائم، أو المخططات التنظيمية.

challenge icon

التحدي

متوسط

في هذا التحدي، ستقوم بتنفيذ هيكل نظام ملفات باستخدام نمط التصميم التركيبي (Composite design pattern). يسمح لك نمط Composite بتكوين كائنات في هياكل شجرية لتمثيل تسلسلات هرمية من الجزء إلى الكل، مع معاملة الكائنات الفردية وتركيبات الكائنات بشكل موحد.

يتكون نمط Composite من:

  • Component: فئة مجردة (abstract class) تحدد الواجهة المشتركة لجميع الفئات الملموسة
  • Leaf: يمثل الكائنات النهائية في التركيبة والتي لا تحتوي على عناصر فرعية
  • Composite: يحدد السلوك للمكونات التي لها أبناء ويقوم بتخزين المكونات الأبناء

ستقوم بتنفيذ نظام ملفات يحتوي على:

  • فئة FileSystemComponent مجردة (Component)
  • فئة File (Leaf)
  • فئة Directory (Composite)
  • فئة FileSystem لإدارة الهيكل العام
  1. قم بتنفيذ الفئة الأساسية المجردة مع الأساليب المجردة المناسبة
  2. قم بإنشاء تطبيقات ملموسة للملفات والمجلدات
  3. تأكد من أن المجلدات يمكن أن تحتوي على ملفات ومجلدات أخرى
  4. قم بتنفيذ العمليات العودية (recursive operations) مثل حساب الحجم والعرض
  5. أضف العمليات القائمة على المسار لإضافة المكونات وإزالتها والعثور عليها
  6. تعامل مع حالات الخطأ بشكل مناسب
  7. تأكد من التغليف (encapsulation) السليم لخصائص المكونات

ورقة مرجعية

يعامل نمط التركيب (Composite Pattern) الكائنات الفردية ومجموعات الكائنات بشكل موحد من خلال إنشاء هياكل شجرية حيث تشترك كل من العناصر الفردية والمجموعات في نفس الواجهة.

مثال بسيط لنظام ملفات:

class File:
    def __init__(self, name, size):
        self.name = name
        self.size = size
    
    def get_size(self):
        return self.size
    
    def display(self):
        return f"File: {self.name} ({self.size}KB)"

class Folder:
    def __init__(self, name):
        self.name = name
        self.children = []
    
    def add(self, item):
        self.children.append(item)
    
    def get_size(self):
        total = 0
        for child in self.children:
            total += child.get_size()
        return total
    
    def display(self):
        result = f"Folder: {self.name}"
        for child in self.children:
            result += f"\n  {child.display()}"
        return result

بناء الهيكل:

# إنشاء ملفات
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)

# إنشاء مجلدات
documents = Folder("Documents")
root = Folder("Root")

# بناء الهيكل الشجري
documents.add(file1)
root.add(documents)

# الاستخدام بشكل موحد
print(f"Root size: {root.get_size()}KB")
print(root.display())

مثال لنظام قائمة طعام:

class MenuItem:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def get_price(self):
        return self.price
    
    def show(self):
        return f"{self.name}: ${self.price}"

class Menu:
    def __init__(self, name):
        self.name = name
        self.items = []
    
    def add(self, item):
        self.items.append(item)
    
    def get_price(self):
        total = 0
        for item in self.items:
            total += item.get_price()
        return total
    
    def show(self):
        result = f"{self.name} Menu:"
        for item in self.items:
            result += f"\n  {item.show()}"
        return result

مكونات النمط:

  • المكون (Component): فئة مجردة تحدد الواجهة المشتركة
  • الورقة (Leaf): الكائنات النهائية التي لا تحتوي على عناصر فرعية (File, MenuItem)
  • المركب (Composite): الكائنات التي تحتوي على أبناء وتنفذ نفس الواجهة (Folder, Menu)

الفوائد الرئيسية: يعامل الكائنات الفردية والمجموعات بشكل موحد، مما يسهل العمل مع الهياكل الشجرية مثل أنظمة الملفات، أو القوائم، أو المخططات التنظيمية.

جرّب بنفسك

# استيراد جميع الفئات اللازمة
from file_system import FileSystem
from directory import Directory
from file import File

# معالج حالات الاختبار الشامل
test_case = input()

if test_case == "basic_file_test":
    file = File("test.txt", 100)
    print(f"Name: {file.name}")
    print(f"Size: {file.get_size()} KB")
    print(file.display())

elif test_case == "basic_directory_test":
    documents = Directory("Documents")
    file1 = File("resume.pdf", 250)
    file2 = File("cover_letter.doc", 180)
    documents.add(file1)
    documents.add(file2)
    print(f"Total size: {documents.get_size()} KB")
    print(documents.display())

elif test_case == "file_system_basic_test":
    fs = FileSystem()
    readme = File("README.md", 50)
    fs.add_to_path("/", readme)
    print(fs.display())
    print(f"Total system size: {fs.get_total_size()} KB")

elif test_case == "nested_directory_test":
    fs = FileSystem()
    docs = Directory("Documents")
    projects = Directory("Projects")
    
    fs.add_to_path("/", docs)
    fs.add_to_path("/Documents", projects)
    
    project_file = File("main.py", 300)
    readme = File("README.md", 75)
    
    fs.add_to_path("/Documents/Projects", project_file)
    fs.add_to_path("/Documents/Projects", readme)
    
    print(fs.display())

elif test_case == "path_operations_test":
    fs = FileSystem()
    
    # إنشاء هيكل المجلدات
    docs = Directory("Documents")
    projects = Directory("Projects")
    fs.add_to_path("/", docs)
    fs.add_to_path("/Documents", projects)
    
    # إضافة الملفات
    file1 = File("notes.txt", 120)
    file2 = File("project1.py", 450)
    fs.add_to_path("/Documents", file1)
    fs.add_to_path("/Documents/Projects", file2)
    
    # اختبار عمليات المسار
    retrieved_docs = fs.get_from_path("/Documents")
    retrieved_file = fs.get_from_path("/Documents/Projects/project1.py")
    
    print(f"Documents directory size: {retrieved_docs.get_size()} KB")
    print(f"Retrieved file: {retrieved_file.name} ({retrieved_file.get_size()} KB)")

elif test_case == "file_validation_test":
    try:
        invalid_file = File("negative.txt", -50)
        print("Validation failed - should have raised ValueError")
    except ValueError as e:
        print(f"Caught expected error: {e}")
    
    # اختبار عمليات NotImplementedError
    valid_file = File("test.txt", 100)
    try:
        valid_file.add(File("other.txt", 50))
    except NotImplementedError as e:
        print(f"Add operation error: {e}")
    
    try:
        valid_file.get_component("nonexistent")
    except NotImplementedError as e:
        print(f"Get component error: {e}")

elif test_case == "directory_duplicate_test":
    directory = Directory("TestDir")
    file1 = File("duplicate.txt", 100)
    file2 = File("duplicate.txt", 200)
    
    directory.add(file1)
    print("First file added successfully")
    
    try:
        directory.add(file2)
        print("Duplicate check failed")
    except ValueError as e:
        print(f"Caught expected duplicate error: {e}")

elif test_case == "component_removal_test":
    directory = Directory("TestDir")
    file1 = File("file1.txt", 100)
    file2 = File("file2.txt", 150)
    file3 = File("file3.txt", 200)
    
    directory.add(file1)
    directory.add(file2)
    directory.add(file3)
    
    print("Initial state:")
    print(directory.display())
    
    directory.remove(file2)
    print("\nAfter removing file2.txt:")
    print(directory.display())
    
    try:
        nonexistent = File("ghost.txt", 50)
        directory.remove(nonexistent)
    except ValueError as e:
        print(f"\nRemoval error: {e}")

elif test_case == "recursive_search_test":
    root_dir = Directory("root")
    subdir1 = Directory("subdir1")
    subdir2 = Directory("subdir2")
    
    file1 = File("target.txt", 100)
    file2 = File("other.txt", 150)
    file3 = File("deep.txt", 200)
    
    root_dir.add(subdir1)
    subdir1.add(subdir2)
    subdir1.add(file1)
    subdir2.add(file3)
    root_dir.add(file2)
    
    # البحث عن الملفات الموجودة
    found1 = root_dir.find_component_recursive("target.txt")
    found2 = root_dir.find_component_recursive("deep.txt")
    not_found = root_dir.find_component_recursive("missing.txt")
    
    print(f"Found target.txt: {found1.name if found1 else 'Not found'}")
    print(f"Found deep.txt: {found2.name if found2 else 'Not found'}")
    print(f"Found missing.txt: {not_found.name if not_found else 'Not found'}")

elif test_case == "size_calculation_test":
    fs = FileSystem()
    
    # إنشاء هيكل معقد
    docs = Directory("Documents")
    images = Directory("Images")
    
    fs.add_to_path("/", docs)
    fs.add_to_path("/", images)
    
    # إضافة ملفات بأحجام معروفة
    doc1 = File("doc1.txt", 100)
    doc2 = File("doc2.txt", 200)
    img1 = File("img1.jpg", 500)
    img2 = File("img2.png", 300)
    
    fs.add_to_path("/Documents", doc1)
    fs.add_to_path("/Documents", doc2)
    fs.add_to_path("/Images", img1)
    fs.add_to_path("/Images", img2)
    
    # حساب الأحجام
    docs_size = fs.get_from_path("/Documents").get_size()
    images_size = fs.get_from_path("/Images").get_size()
    total_size = fs.get_total_size()
    
    print(f"Documents size: {docs_size} KB")
    print(f"Images size: {images_size} KB")
    print(f"Total size: {total_size} KB")
    print(f"Sum verification: {docs_size + images_size == total_size}")

elif test_case == "display_formatting_test":
    root = Directory("root")
    level1 = Directory("level1")
    level2 = Directory("level2")
    
    file1 = File("root_file.txt", 100)
    file2 = File("level1_file.txt", 200)
    file3 = File("level2_file.txt", 300)
    
    root.add(file1)
    root.add(level1)
    level1.add(file2)
    level1.add(level2)
    level2.add(file3)
    
    print("Formatted directory structure:")
    print(root.display())

elif test_case == "file_system_path_test":
    fs = FileSystem()
    
    try:
        # إنشاء الهيكل
        fs.add_to_path("/", Directory("home"))
        fs.add_to_path("/home", Directory("user"))
        fs.add_to_path("/home/user", File("profile.txt", 150))
        
        # اختبار الاسترجاع
        user_dir = fs.get_from_path("/home/user")
        profile = fs.get_from_path("/home/user/profile.txt")
        
        print(f"User directory: {user_dir.name}")
        print(f"Profile file: {profile.name}")
        
        # اختبار الإزالة
        fs.remove_from_path("/home/user/profile.txt")
        print("Profile removed successfully")
        
        # محاولة الوصول إلى ملف تمت إزالته
        try:
            fs.get_from_path("/home/user/profile.txt")
        except ValueError as e:
            print(f"Expected error accessing removed file: {e}")
            
    except ValueError as e:
        print(f"Path operation error: {e}")

elif test_case == "empty_directory_test":
    empty_dir = Directory("Empty")
    
    print(f"Empty directory size: {empty_dir.get_size()} KB")
    print("Empty directory display:")
    print(empty_dir.display())
    
    result = empty_dir.get_component("nonexistent")
    print(f"Get nonexistent component: {result}")

elif test_case == "name_property_test":
    file = File("original.txt", 100)
    directory = Directory("OriginalDir")
    
    print(f"Original file name: {file.name}")
    print(f"Original directory name: {directory.name}")
    
    # اختبار تعيين أسماء صالحة
    file.name = "renamed.txt"
    directory.name = "RenamedDir"
    
    print(f"Renamed file: {file.name}")
    print(f"Renamed directory: {directory.name}")
    
    # اختبار تعيين اسم فارغ
    try:
        file.name = ""
        print("Empty name validation failed")
    except ValueError as e:
        print(f"Empty name error: {e}")

elif test_case == "large_file_system_test":
    fs = FileSystem()
    
    # إنشاء مجلدات وملفات متعددة
    directories = ["Documents", "Images", "Videos", "Music"]
    file_counts = [5, 3, 2, 4]
    base_sizes = [100, 500, 1000, 200]
    
    total_files = 0
    total_directories = len(directories)
    
    for i, dir_name in enumerate(directories):
        fs.add_to_path("/", Directory(dir_name))
        
        for j in range(file_counts[i]):
            file_name = f"file_{j+1}.ext"
            file_size = base_sizes[i] + (j * 50)
            fs.add_to_path(f"/{dir_name}", File(file_name, file_size))
            total_files += 1
    
    print(f"Created {total_directories} directories")
    print(f"Created {total_files} files")
    print(f"Total system size: {fs.get_total_size()} KB")
    print("\nSystem structure:")
    print(fs.display())

elif test_case == "edge_cases_test":
    # اختبار ملف بحجم 0
    zero_file = File("empty.txt", 0)
    print(f"Zero size file: {zero_file.get_size()} KB")
    
    # اختبار مجلد باسم طويل جداً
    long_name = "A" * 100
    long_dir = Directory(long_name)
    print(f"Long directory name length: {len(long_dir.name)}")
    
    # اختبار هيكل متداخل بعمق
    current = Directory("level0")
    root = current
    
    for i in range(1, 6):
        next_level = Directory(f"level{i}")
        current.add(next_level)
        current = next_level
    
    # إضافة ملف في أعمق مستوى
    deep_file = File("deep.txt", 100)
    current.add(deep_file)
    
    print("Deep nesting test:")
    print(root.display())
    
    # اختبار العمليات على الهياكل الفارغة
    empty = Directory("Empty")
    try:
        empty.remove(File("ghost.txt", 50))
    except ValueError as e:
        print(f"Empty structure removal error: {e}")
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming