Menu
Coddy logo textTech

תבנית המצב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-PHP של Coddy. שיעור 79 מתוך 91.

תבנית State היא תבנית עיצוב התנהגותית המאפשרת לאובייקט לשנות את ההתנהגות שלו כאשר המצב הפנימי שלו משתנה. במקום להשתמש בתנאים מורכבים כדי לטפל במצבים שונים, עוטפים כל מצב במחלקה משלו ומעבירים את ההתנהגות לאובייקט המצב הנוכחי.

חשבו על מכונת ממכר אוטומטית. היא מתנהגת באופן שונה בהתאם לשאלה אם היא ממתינה לכסף, קיבלה תשלום או מוציאה מוצר.

לכל מצב יש כללים משלו לגבי הפעולות המותרות. תבנית המצב ממדלת זאת באמצעות יצירת מחלקה לכל מצב.

<?php
interface OrderState {
    public function proceed(Order $order): string;
    public function getStatus(): string;
}

class PendingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ProcessingState());
        return "Order moved to processing";
    }
    
    public function getStatus(): string {
        return "Pending";
    }
}

class ProcessingState implements OrderState {
    public function proceed(Order $order): string {
        $order->setState(new ShippedState());
        return "Order has been shipped";
    }
    
    public function getStatus(): string {
        return "Processing";
    }
}

class ShippedState implements OrderState {
    public function proceed(Order $order): string {
        return "Order already shipped";
    }
    
    public function getStatus(): string {
        return "Shipped";
    }
}

מחלקת ההקשר מחזיקה הפניה למצב הנוכחי ומעבירה אליו קריאות:

<?php
class Order {
    private OrderState $state;
    
    public function __construct() {
        $this->state = new PendingState();
    }
    
    public function setState(OrderState $state): void {
        $this->state = $state;
    }
    
    public function proceed(): string {
        return $this->state->proceed($this);
    }
    
    public function getStatus(): string {
        return $this->state->getStatus();
    }
}

$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();

פלט:

Pending
Order moved to processing
Processing
Order has been shipped
Shipped

תבנית State מבטלת הצהרות switch מסורבלות ומקלה על הוספת מצבים חדשים. כל מחלקת מצב אחראית לדעת איזה מצב מגיע אחריה, וכך הלוגיקה נשארת מאורגנת וניתנת לתחזוקה.

challenge icon

אתגר

קל

בוא נבנה מערכת לניהול תהליך עבודה של מסמכים באמצעות תבנית State. מסמכים במערכת לניהול תוכן עוברים שלבים שונים — טיוטה, סקירה ופרסום — ולכל שלב יש כללים משלו לגבי הפעולות המותרות ומה קורה כשמנסים לקדם את המסמך.

תארגן את הקוד שלך בארבעה קבצים:

  • DocumentState.php: הגדר ממשק DocumentState שמגדיר את החוזה לכל מצבי המסמך. צריכות להיות בו שתי פונקציות: publish(Document $document): string לניסיון לקדם את המסמך, ו-getStatus(): string להחזרת שם המצב הנוכחי.
  • States.php: כלול את הממשק DocumentState וצור שלוש מחלקות מצבים שמממשות אותו:
    • DraftState: כאשר קוראים ל-publish(), המסמך עובר למצב ReviewState ומוחזר "Document sent for review". הסטטוס שלו הוא "Draft".
    • ReviewState: כאשר קוראים ל-publish(), המסמך עובר למצב PublishedState ומוחזר "Document published". הסטטוס שלו הוא "Under Review".
    • PublishedState: כאשר קוראים ל-publish(), מוחזר "Document is already published" בלי לשנות את המצב. הסטטוס שלו הוא "Published".
  • Document.php: כלול את הקובץ States וצור מחלקה Document שמתפקדת כהקשר. המסמך שלך צריך:
    • לקבל כותרת בבנאי ולהתחיל במצב DraftState
    • להכיל פונקציה setState(DocumentState $state): void לשינוי המצב הנוכחי
    • להכיל פונקציה publish(): string שמעבירה את הקריאה לפונקציה publish() של המצב הנוכחי
    • להכיל פונקציה getStatus(): string שמחזירה את הסטטוס של המצב הנוכחי
  • main.php: כלול את קובץ Document. יתקבל קלט אחד: מספר הפעמים לקרוא ל-publish() עבור המסמך.

    צור Document עם הכותרת "My Article". תחילה, הדפס את הסטטוס ההתחלתי. לאחר מכן, קרא ל-publish() את מספר הפעמים שצוין, והדפס את התוצאה של כל קריאה בשורה חדשה. לבסוף, הדפס את הסטטוס הסופי.

    פורמט הפלט:

    Status: [initial status]
    [publish result 1]
    [publish result 2]
    ...
    Status: [final status]

כל מחלקת מצב יודעת מהו המצב הבא בתהליך העבודה, וכך לוגיקת המעבר נשארת מסודרת וקלה לשינוי. הוספת מצב חדש כמו "Archived" תצריך רק יצירת מחלקה חדשה ועדכון המעברים הרלוונטיים.

נסו בעצמכם

<?php

require_once 'Document.php';

// קרא את הקלט - מספר הפעמים שיש לקרוא ל-publish()
$n = intval(trim(fgets(STDIN)));

// TODO: צור Document עם הכותרת "My Article"

// TODO: הדפס את המצב ההתחלתי בפורמט: "Status: [status]"

// TODO: קרא ל-publish() n פעמים, והדפס כל תוצאה בשורה חדשה

// TODO: הדפס את המצב הסופי בפורמט: "Status: [status]"

?>
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר PHP אונליין