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

نمط التركيب

جزء من قسم البرمجة كائنية التوجه في رحلة Python على Coddy. الدرس 54 من 64.

يتعامل نمط التركيب مع الكائنات الفردية ومجموعات الكائنات بطريقة موحّدة. وينشئ هياكل شجرية تتشارك فيها العناصر المفردة ومجموعات العناصر الواجهةَ نفسها.

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

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)

استخدم البنية المركبة:

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

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

challenge icon

التحدي

متوسط

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

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

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

ستنفّذ نظام ملفات يتضمن:

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

المسارات هي سلاسل نصية مثل /Documents/Projects. يضيف add_to_path مكوّنًا داخل المجلد الموجود في المسار (/ هو الجذر)، بينما يشير get_from_path وremove_from_path إلى الملف أو المجلد في المسار. توضّح تعليقات TODO في كل ملف الخطوات الدقيقة ورسائل الخطأ.

جرّب بنفسك

# استيراد جميع الفئات الضرورية
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()
    
    # Create directory structure
    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اختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Python عبر الإنترنت