2.1 תבניות יצירה הרצאה
תבניות יצירה - Creational Patterns¶
תבניות יצירה עוסקות ביצירת אובייקטים. הן פותרות את הבעיה: "איך יוצרים אובייקט בצורה גמישה שניתן לשנות בעתיד?"
נכיר שלוש תבניות יצירה נפוצות: Factory, Singleton ו-Builder.
Factory - מפעל¶
הבעיה: הקוד שלנו צריך ליצור אובייקטים, אבל אנחנו רוצים לשמור גמישות לגבי איזה אובייקט נוצר.
הפתרון: במקום לקרוא ישירות ל-constructor, קוראים לפונקציית "מפעל" שמחליטה מה ליצור.
דוגמה בלי Factory¶
@app.post("/tasks/{task_id}/notify")
def notify(task_id: int, channel: str):
if channel == "email":
notifier = EmailNotifier()
notifier.send(...)
elif channel == "sms":
notifier = SMSNotifier()
notifier.send(...)
elif channel == "push":
notifier = PushNotifier()
notifier.send(...)
# בכל פעם שמוסיפים ערוץ - צריך לשנות כאן
Factory Method¶
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, recipient: str, message: str):
pass
class EmailNotifier(Notifier):
def send(self, recipient: str, message: str):
print(f"מייל ל-{recipient}: {message}")
class SMSNotifier(Notifier):
def send(self, recipient: str, message: str):
print(f"SMS ל-{recipient}: {message}")
class PushNotifier(Notifier):
def send(self, recipient: str, message: str):
print(f"Push ל-{recipient}: {message}")
def create_notifier(channel: str) -> Notifier:
notifiers = {
"email": EmailNotifier,
"sms": SMSNotifier,
"push": PushNotifier
}
notifier_class = notifiers.get(channel)
if notifier_class is None:
raise ValueError(f"ערוץ לא ידוע: {channel}")
return notifier_class()
# שימוש
@app.post("/tasks/{task_id}/notify")
def notify(task_id: int, channel: str):
notifier = create_notifier(channel)
notifier.send(...)
עכשיו להוסיף ערוץ חדש - פשוט מוסיפים מחלקה ומוסיפים ל-dict. הroute לא משתנה.
Singleton - יחיד¶
הבעיה: חלק מהאובייקטים צריכים להיות ממופים בדיוק פעם אחת בתוכנה - חיבור למסד נתונים, logger, cache connection.
הפתרון: Singleton מבטיח שמחלקה מסוימת תיווצר רק פעם אחת לאורך כל חיי התוכנה.
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
print("יוצר חיבור חדש למסד הנתונים")
cls._instance = super().__new__(cls)
import sqlite3
cls._instance.connection = sqlite3.connect("taskflow.db")
return cls._instance
def execute(self, query: str, params: tuple = ()):
return self.connection.execute(query, params)
# בכל מקום בקוד
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True - אותו אובייקט!
Singleton בפייתון - גישה פשוטה יותר¶
בפייתון, module-level variables הם בעצמם singletons - נוצרים פעם אחת כשה-module נטען.
# database.py
import sqlite3
_connection = None
def get_connection():
global _connection
if _connection is None:
_connection = sqlite3.connect("taskflow.db")
return _connection
אזהרה על Singleton¶
Singleton נחשב לעיתים כ-"anti-pattern" כי הוא יוצר תלות גלובלית שקשה לבדוק. בדיקה שמשתמשת ב-Singleton אמיתי לא יכולה להחליף אותו ב-mock. מומלץ להשתמש בזה במשורה.
Builder - בונה¶
הבעיה: יצירת אובייקטים מורכבים עם הרבה פרמטרים אופציונליים. Constructor עם 10 פרמטרים קשה לשימוש.
# קשה לקרוא - מה כל פרמטר?
task = Task("בדיקה", "תיאור", 1, "high", True, None, "red", 3, datetime.now(), None)
הפתרון: Builder מאפשר לבנות אובייקט שלב אחרי שלב, עם שמות ברורים.
from datetime import datetime
class TaskBuilder:
def __init__(self, title: str, owner_id: int):
self._title = title
self._owner_id = owner_id
self._description = ""
self._priority = "normal"
self._due_date = None
self._tags = []
self._color = None
def with_description(self, description: str) -> "TaskBuilder":
self._description = description
return self
def with_priority(self, priority: str) -> "TaskBuilder":
if priority not in {"low", "normal", "high", "critical"}:
raise ValueError(f"עדיפות לא תקינה: {priority}")
self._priority = priority
return self
def with_due_date(self, due_date: datetime) -> "TaskBuilder":
self._due_date = due_date
return self
def with_tags(self, *tags: str) -> "TaskBuilder":
self._tags = list(tags)
return self
def with_color(self, color: str) -> "TaskBuilder":
self._color = color
return self
def build(self) -> dict:
return {
"title": self._title,
"owner_id": self._owner_id,
"description": self._description,
"priority": self._priority,
"due_date": self._due_date,
"tags": self._tags,
"color": self._color
}
# שימוש - קריא מאוד
task = (
TaskBuilder("תקן באג ב-login", owner_id=5)
.with_description("משתמשים מדווחים על בעיה בהתחברות מדפדפן Safari")
.with_priority("high")
.with_due_date(datetime(2026, 4, 10))
.with_tags("bug", "auth", "safari")
.with_color("red")
.build()
)
מתי להשתמש ב-Builder?¶
- כשיש יותר מ-4-5 פרמטרים, חלקם אופציונליים
- כשרוצים לאפשר קריאות גבוהה ("fluent interface")
- כשיצירת האובייקט כוללת ולידציה או לוגיקה
סיכום¶
| תבנית | מתי להשתמש |
|---|---|
| Factory | כשרוצים גמישות באיזה סוג אובייקט ייווצר |
| Singleton | כשצריך דיוק מופע אחד (connection, logger) |
| Builder | כשיצירת אובייקט מורכבת עם הרבה פרמטרים |