نمط التركيب
جزء من قسم 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) نفسها، مما يسهل العمل مع الهياكل الشجرية مثل أنظمة الملفات، أو القوائم، أو المخططات التنظيمية.
التحدي
متوسطفي هذا التحدي، ستقوم بتنفيذ هيكل نظام ملفات باستخدام نمط التصميم التركيبي (Composite design pattern). يسمح لك نمط Composite بتكوين كائنات في هياكل شجرية لتمثيل تسلسلات هرمية من الجزء إلى الكل، مع معاملة الكائنات الفردية وتركيبات الكائنات بشكل موحد.
يتكون نمط Composite من:
- Component: فئة مجردة (abstract class) تحدد الواجهة المشتركة لجميع الفئات الملموسة
- Leaf: يمثل الكائنات النهائية في التركيبة والتي لا تحتوي على عناصر فرعية
- Composite: يحدد السلوك للمكونات التي لها أبناء ويقوم بتخزين المكونات الأبناء
ستقوم بتنفيذ نظام ملفات يحتوي على:
- فئة
FileSystemComponentمجردة (Component) - فئة
File(Leaf) - فئة
Directory(Composite) - فئة
FileSystemلإدارة الهيكل العام
- قم بتنفيذ الفئة الأساسية المجردة مع الأساليب المجردة المناسبة
- قم بإنشاء تطبيقات ملموسة للملفات والمجلدات
- تأكد من أن المجلدات يمكن أن تحتوي على ملفات ومجلدات أخرى
- قم بتنفيذ العمليات العودية (recursive operations) مثل حساب الحجم والعرض
- أضف العمليات القائمة على المسار لإضافة المكونات وإزالتها والعثور عليها
- تعامل مع حالات الخطأ بشكل مناسب
- تأكد من التغليف (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}")يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsمعامل selfMethodsAttributesدالة البناء (__init__)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةدالة ()superإعادة تعريف الدوال (Method Overriding)الوراثة المتعددةترتيب استدعاء الدوال (Method Resolution Order)مراجعة - هيكلية الموظفين5تعدد الأشكال
مراجعة إعادة تعريف الدوالمفهوم Duck Typingالأصناف والدوال المجردةتصميم الواجهاتملخص - حاسبة الأشكال