Menu
Coddy logo textTech

תבנית המצב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 74 מתוך 87.

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

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

interface State {
    void handle(Context context);
}

class Context {
    private State state;
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle(this);
    }
}

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

class IdleState implements State {
    public void handle(Context context) {
        System.out.println("Idle: Insert coin to start");
        context.setState(new ActiveState());
    }
}

class ActiveState implements State {
    public void handle(Context context) {
        System.out.println("Active: Processing...");
        context.setState(new IdleState());
    }
}

ההקשר מעביר בקשות למצב הנוכחי שלו, וההתנהגות משתנה אוטומטית ככל שהמצב משתנה:

Context context = new Context();
context.setState(new IdleState());

context.request();  // מצב המתנה: יש להכניס מטבע כדי להתחיל
context.request();  // מצב פעיל: מעבד...

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

challenge icon

אתגר

קל

בואו נבנה מערכת לניהול זרימת עבודה של מסמכים באמצעות תבנית State! תיצרו מערכת שמדגימה כיצד מסמך עובר בין שלבים שונים, Draft, Review ו-Published, כאשר כל מצב קובע אילו פעולות זמינות וכיצד המסמך מתנהג כשמנסים לערוך או לאשר אותו.

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

  • DocumentState.java: הגדירו את הממשק DocumentState שכל המצבים יממשו. עליו להצהיר על שתי שיטות: edit(Document doc) לניסיון לערוך את המסמך, ו-approve(Document doc) לניסיון לאשר את המסמך או להעביר אותו לשלב הבא.
  • States.java: צרו שלוש מחלקות מצב קונקרטיות שמממשות את DocumentState:

    DraftState - כשקוראים ל-edit, הדפיסו Editing draft.... כשקוראים ל-approve, הדפיסו Draft approved. Moving to review. והעבירו את המסמך למצב ReviewState.

    ReviewState - כשקוראים ל-edit, הדפיסו Cannot edit during review. (ללא שינוי מצב). כשקוראים ל-approve, הדפיסו Review complete. Publishing document. והעבירו את המסמך למצב PublishedState.

    PublishedState - כשקוראים ל-edit, הדפיסו Cannot edit published document.. כשקוראים ל-approve, הדפיסו Document already published.. אף אחת מהפעולות לא משנה את המצב.

  • Document.java: צרו את מחלקת ההקשר שמנהלת את המצב הנוכחי. למחלקה Document שלכם צריכים להיות:

    שדה פרטי שמכיל את ה-DocumentState הנוכחי, ומאותחל ל-DraftState בבנאי.

    שיטה setState(DocumentState state) שמשנה את המצב הנוכחי.

    שיטה edit() שמעבירה את הקריאה לשיטת edit של המצב הנוכחי.

    שיטה approve() שמעבירה את הקריאה לשיטת approve של המצב הנוכחי.

    שיטה getStatus() שמחזירה את שם המחלקה הפשוט של המצב הנוכחי (השתמשו ב-getClass().getSimpleName()).

  • Main.java: הדגימו את מערכת המצבים שלכם! תקבלו קלט אחד: רצף פעולות כמחרוזת המופרדת בפסיקים, כאשר כל פעולה היא edit או approve (לדוגמה: edit,approve,edit,approve).

    צרו Document והדפיסו את המצב ההתחלתי שלו בתבנית Status: [state]. לאחר מכן עבדו על כל פעולה לפי הסדר, וקראו לשיטה המתאימה במסמך. אחרי כל פעולה, הדפיסו את המצב הנוכחי.

תקבלו קלט אחד: מחרוזת של פעולות המופרדות בפסיקים.

לדוגמה, עבור הקלט edit,approve,approve, הפלט שלכם יהיה:

Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedState

שימו לב שאותה קריאה ל-approve() מפיקה תוצאות שונות בהתאם למצב הנוכחי של המסמך. זוהי המהות של תבנית State! המסמך לא משתמש בתנאים כדי לבדוק את מצבו; במקום זאת, הוא מעביר את האחריות להתנהגות לאובייקט המצב הנוכחי, שמטפל בפעולה כראוי ומפעיל מעברים כשצריך.

נסו בעצמכם

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        
        // TODO: צור Document חדש
        
        // TODO: הדפס את המצב ההתחלתי בפורמט "Status: [state]"
        
        // TODO: פצל את הקלט לפי פסיקים כדי לקבל פעולות נפרדות
        
        // TODO: עבור על כל פעולה
        // - אם הפעולה היא "edit", קרא ל־edit() על document
        // - אם הפעולה היא "approve", קרא ל־approve() על document
        // - אחרי כל פעולה, הדפס את המצב הנוכחי
    }
}
quiz iconבחנו את עצמכם

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

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

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