Menu
Coddy logo textTech

תבנית המצב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 100 מתוך 104.

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

התבנית מורכבת מהקשר שמחזיק הפניה למצב הנוכחי, ממשק מצב שמגדיר התנהגות ספציפית למצב, וממצבים קונקרטיים שמממשים התנהגות עבור כל מצב:

#include <iostream>
#include <memory>

class Document;  // הצהרה מוקדמת

// ממשק מצב
class DocumentState {
public:
    virtual void publish(Document& doc) = 0;
    virtual std::string getName() const = 0;
    virtual ~DocumentState() = default;
};

// הקשר
class Document {
    std::unique_ptr<DocumentState> state;
public:
    Document();
    void setState(std::unique_ptr<DocumentState> newState) {
        state = std::move(newState);
    }
    void publish() { state->publish(*this); }
    std::string getStateName() const { return state->getName(); }
};

// מצבים קונקרטיים
class Draft : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Draft"; }
};

class Review : public DocumentState {
public:
    void publish(Document& doc) override;
    std::string getName() const override { return "Review"; }
};

class Published : public DocumentState {
public:
    void publish(Document& doc) override {
        std::cout << "Already published\n";
    }
    std::string getName() const override { return "Published"; }
};

void Draft::publish(Document& doc) {
    std::cout << "Moving to review\n";
    doc.setState(std::make_unique<Review>());
}

void Review::publish(Document& doc) {
    std::cout << "Publishing document\n";
    doc.setState(std::make_unique<Published>());
}

Document::Document() : state(std::make_unique<Draft>()) {}

כל מצב מטפל בפעולה publish() באופן שונה ואחראי למעבר למצב הבא. ה־Document לא צריך להכיר את לוגיקת המעבר — הוא פשוט מעביר את האחריות למצב הנוכחי. כך נמנעים ממשפטי switch גדולים, והוספת מצבים חדשים נעשית פשוטה.

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

challenge icon

אתגר

קל

בואו נבנה בקר רמזור באמצעות תבנית State. תיצור מערכת שבה הרמזור עובר במחזוריות בין מצבים שונים (Red, Yellow, Green), כאשר כל מצב קובע מה קורה כשהרמזור משתנה ואיזו הודעה מוצגת. זוהי דוגמה קלאסית לתבנית State: ההתנהגות של הרמזור תלויה לחלוטין במצב הנוכחי שלו.

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

  • TrafficLightState.h: הגדר את הממשק של המצבים ואת כל מחלקות המצבים הקונקרטיות.

    צור מחלקה אבסטרקטית בשם TrafficLightState עם:

    • מתודה וירטואלית טהורה change(TrafficLight& light) שמטפלת במעבר למצב הבא
    • מתודה וירטואלית טהורה getColor() שמחזירה את הצבע הנוכחי כמחרוזת
    • מתודה וירטואלית טהורה getAction() שמחזירה את הפעולה שעל הנהגים לבצע (למשל, "Stop", "Caution", "Go")
    • הורס וירטואלי

    ממש שלושה מצבים קונקרטיים:

    • RedState: הצבע הוא "Red", הפעולה היא "Stop", ועובר ל-Green
    • YellowState: הצבע הוא "Yellow", הפעולה היא "Caution", ועובר ל-Red
    • GreenState: הצבע הוא "Green", הפעולה היא "Go", ועובר ל-Yellow

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

  • TrafficLight.h: צור את מחלקת ההקשר שמחזיקה את המצב הנוכחי.

    מחלקת TrafficLight שלך צריכה להכיל std::unique_ptr<TrafficLightState> ולהתחיל כברירת מחדל במצב Red. ממש את המתודות הבאות:

    • setState(std::unique_ptr<TrafficLightState> newState): משנה את המצב הנוכחי
    • change(): מעבירה את הטיפול למתודת change של המצב הנוכחי
    • display(): מדפיסה את מצב הרמזור בתבנית: [Color]: [Action]
  • main.cpp: הדגם את המעבר המחזורי של הרמזור בין המצבים.

    קרא קלט אחד: מספר שינויי המצב שיש לבצע (מספר שלם).

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

לדוגמה, עבור הקלט 3:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop

עבור הקלט 6:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

שים לב כיצד הרמזור עובר בין המצבים שלו במחזוריות צפויה: Red → Green → Yellow → Red. כל מצב יודע איזה מצב מגיע אחריו ומטפל במעבר שלו בעצמו. מחלקת TrafficLight אינה זקוקה ללוגיקה מותנית כדי לקבוע מה יקרה. היא פשוט מעבירה את הטיפול למצב שבו היא נמצאת כרגע.

נסו בעצמכם

#include <iostream>
#include "TrafficLight.h"

using namespace std;

int main() {
    // קרא את מספר שינויי המצב
    int numChanges;
    cin >> numChanges;
    
    // TODO: צור אובייקט TrafficLight
    
    // TODO: הצג את המצב ההתחלתי
    
    // TODO: בצע את מספר השינויים שצוין
    // לאחר כל שינוי, הצג את המצב הנוכחי
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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