לדלג לתוכן

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 כשיצירת אובייקט מורכבת עם הרבה פרמטרים