לדלג לתוכן

2.3 תבניות התנהגות פתרון

תרגיל 1 - Observer - פתרון

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class AssignmentEvent:
    task_id: int
    assignee_id: int
    assigner_id: int
    task_title: str


class AssignmentListener(ABC):
    @abstractmethod
    def on_assignment(self, event: AssignmentEvent):
        pass


class AssignmentEventBus:
    def __init__(self):
        self._listeners: list[AssignmentListener] = []

    def subscribe(self, listener: AssignmentListener):
        self._listeners.append(listener)

    def publish(self, event: AssignmentEvent):
        for listener in self._listeners:
            listener.on_assignment(event)


class EmailAssignmentListener(AssignmentListener):
    def on_assignment(self, event: AssignmentEvent):
        print(
            f"[EMAIL] למשתמש {event.assignee_id}: "
            f"הוקצתה לך משימה '{event.task_title}' על ידי {event.assigner_id}"
        )


class InAppNotificationListener(AssignmentListener):
    def on_assignment(self, event: AssignmentEvent):
        print(
            f"[IN-APP] התראה חדשה למשתמש {event.assignee_id}: "
            f"משימה '{event.task_title}' הוקצתה לך"
        )


class TaskService:
    def __init__(self, event_bus: AssignmentEventBus):
        self._event_bus = event_bus
        self._tasks = {
            1: {"id": 1, "title": "תקן באג", "assignee": None},
            2: {"id": 2, "title": "כתוב בדיקות", "assignee": None}
        }

    def assign_task(self, task_id: int, assignee_id: int, assigner_id: int):
        task = self._tasks.get(task_id)
        if not task:
            raise ValueError(f"משימה {task_id} לא נמצאה")
        task["assignee"] = assignee_id
        self._event_bus.publish(AssignmentEvent(
            task_id=task_id,
            assignee_id=assignee_id,
            assigner_id=assigner_id,
            task_title=task["title"]
        ))


bus = AssignmentEventBus()
bus.subscribe(EmailAssignmentListener())
bus.subscribe(InAppNotificationListener())

service = TaskService(bus)
service.assign_task(1, assignee_id=5, assigner_id=1)

תרגיל 2 - Strategy - פתרון

from abc import ABC, abstractmethod
from datetime import date


class TaskFilterStrategy(ABC):
    @abstractmethod
    def filter(self, tasks: list[dict]) -> list[dict]:
        pass


class FilterByStatus(TaskFilterStrategy):
    def __init__(self, status: str):
        self.status = status

    def filter(self, tasks: list[dict]) -> list[dict]:
        return [t for t in tasks if t.get("status") == self.status]


class FilterByOwner(TaskFilterStrategy):
    def __init__(self, owner_id: int):
        self.owner_id = owner_id

    def filter(self, tasks: list[dict]) -> list[dict]:
        return [t for t in tasks if t.get("owner_id") == self.owner_id]


class FilterOverdue(TaskFilterStrategy):
    def filter(self, tasks: list[dict]) -> list[dict]:
        today = date.today().isoformat()
        return [
            t for t in tasks
            if t.get("due_date") and t["due_date"] < today and t.get("status") != "done"
        ]


class CompositeFilter(TaskFilterStrategy):
    def __init__(self, *filters: TaskFilterStrategy):
        self._filters = list(filters)

    def filter(self, tasks: list[dict]) -> list[dict]:
        result = tasks
        for f in self._filters:
            result = f.filter(result)
        return result


# שימוש
tasks = [
    {"id": 1, "title": "תקן באג", "status": "in_progress", "owner_id": 1, "due_date": "2026-01-01"},
    {"id": 2, "title": "כתוב בדיקות", "status": "todo", "owner_id": 2, "due_date": "2026-12-31"},
    {"id": 3, "title": "review קוד", "status": "todo", "owner_id": 1, "due_date": "2026-01-15"},
]

# מסנן משימות פתוחות של user 1 שעברו תאריך יעד
f = CompositeFilter(FilterByOwner(1), FilterOverdue())
print(f.filter(tasks))

תרגיל 3 - Command - פתרון

class DeleteTaskCommand(Command):
    def __init__(self, db: TaskDB, task_id: int):
        self.db = db
        self.task_id = task_id
        self._deleted_task: dict | None = None

    def execute(self) -> None:
        self._deleted_task = self.db.delete(self.task_id)
        if not self._deleted_task:
            raise ValueError(f"משימה {self.task_id} לא נמצאה")

    def undo(self) -> None:
        if self._deleted_task:
            self.db._tasks[self._deleted_task["id"]] = self._deleted_task

    def description(self) -> str:
        title = self._deleted_task["title"] if self._deleted_task else f"id={self.task_id}"
        return f"מחיקת משימה: {title}"


# תרחיש
db = TaskDB()
history = CommandHistory()

# יצירת שתי משימות
history.execute(CreateTaskCommand(db, "משימה ראשונה", owner_id=1))
history.execute(CreateTaskCommand(db, "משימה שנייה", owner_id=1))

print("לפני מחיקה:", db._tasks)

# מחיקת המשימה הראשונה
history.execute(DeleteTaskCommand(db, 1))
print("אחרי מחיקה:", db._tasks)

# undo למחיקה
history.undo()
print("אחרי undo:", db._tasks)

# ודא שהמשימה חזרה
assert 1 in db._tasks, "המשימה הראשונה צריכה לחזור"
print("המשימה שוחזרה בהצלחה!")