תבנית ההרכבה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Python של Coddy. שיעור 54 מתוך 64.
תבנית ה-Composite מתייחסת באופן אחיד לאובייקטים בודדים ולקבוצות של אובייקטים. היא יוצרת מבני עץ שבהם גם פריטים בודדים וגם אוספי פריטים חולקים את אותו ממשק.
הנה רכיבים פשוטים למערכת קבצים:
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נקודה מרכזית: תבנית ה־Composite מאפשרת להתייחס לאובייקטים בודדים ולאוספים של אובייקטים באותו אופן. גם עלים (פריטים בודדים) וגם קומפוזיטים (קבוצות) מממשים את אותו ממשק, וכך קל לעבוד עם מבני עץ כמו מערכות קבצים, תפריטים או תרשימים ארגוניים.
אתגר
בינוניבאתגר הזה תממש מבנה של מערכת קבצים באמצעות תבנית העיצוב Composite. תבנית Composite מאפשרת להרכיב אובייקטים למבני עץ כדי לייצג היררכיות של שלם וחלקיו, תוך התייחסות אחידה לאובייקטים בודדים ולהרכבים של אובייקטים.
תבנית Composite מורכבת מ:
- Component: מחלקה מופשטת שמגדירה את הממשק המשותף לכל המחלקות הקונקרטיות
- Leaf: מייצג אובייקטי קצה בהרכב, ללא תת-רכיבים
- Composite: מגדירה התנהגות לרכיבים שיש להם ילדים, ושומרת את רכיבי הילד
תממש מערכת קבצים הכוללת:
- מחלקה מופשטת
FileSystemComponent(Component) - מחלקה
File(Leaf) - מחלקה
Directory(Composite) - מחלקה
FileSystemלניהול המבנה כולו
- ממש את מחלקת הבסיס המופשטת עם מתודות מופשטות מתאימות
- צור מימושים קונקרטיים לקבצים ולתיקיות
- ודא שתיקיות יכולות להכיל גם קבצים וגם תיקיות אחרות
- ממש פעולות רקורסיביות כמו חישוב גודל והצגה
- הוסף פעולות מבוססות נתיב להוספה, להסרה ולמציאה של רכיבים
- טפל במקרי שגיאה באופן מתאים
- ודא שיש כימוס נכון של מאפייני הרכיבים
נתיבים הם מחרוזות כגון /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()
# יצירת מבנה תיקיות
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}")השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים (OOP)
קבצים חיצונייםמבוא ל-OOPמחלקות לעומת אובייקטיםהפרמטר selfמתודותמאפייניםשיטת הבנאי (__init__)חזרה: מחשבון פשוט5פולימורפיזם
בחינה מחדש של דריסת מתודותטיפוסיות ברווזמחלקות ומתודות אבסטרקטיותעיצוב ממשקיםסיכום – מחשבון צורות8מושגים מתקדמים בתכנות מונחה עצמים
קומפוזיציה לעומת הורשהMixinsמתודות סטטיות ומתודות מחלקהדקורטורים למחלקותמנהלי הקשרתרגלו בעצמכם: קומפיילר Python אונליין