Menu
Coddy logo textTech

תבנית המצב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Dart של Coddy. שיעור 98 מתוך 110.

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

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

abstract class DocumentState {
  void publish(Document doc);
  String getStatus();
}

class DraftState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Moving to review...');
    doc.setState(ReviewState());
  }

  @override
  String getStatus() => 'Draft';
}

class ReviewState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Document published!');
    doc.setState(PublishedState());
  }

  @override
  String getStatus() => 'Under Review';
}

class PublishedState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Already published.');
  }

  @override
  String getStatus() => 'Published';
}

class Document {
  DocumentState _state = DraftState();

  void setState(DocumentState state) => _state = state;
  void publish() => _state.publish(this);
  String getStatus() => _state.getStatus();
}

void main() {
  var doc = Document();
  print(doc.getStatus());  // טיוטה
  doc.publish();           // מועבר לבדיקה...
  print(doc.getStatus());  // בבדיקה
  doc.publish();           // המסמך פורסם!
  print(doc.getStatus());  // פורסם
}

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

challenge icon

אתגר

קל

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

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

  • traffic_light.dart: קובץ זה מכיל את מחלקות המצבים ואת ההקשר של הרמזור. יש ליצור מחלקה מופשטת TrafficLightState עם שתי מתודות: String getColor() שמחזירה את צבע האור הנוכחי, ו-void next(TrafficLight light) שמעבירה את הרמזור למצב הבא. יש לממש שלוש מחלקות מצב קונקרטיות:
    • RedState - מחזירה את RED כצבע שלה ומדפיסה את Stop! Light is red. כאשר קוראים ל-next(), ואז עוברת ל-GreenState
    • GreenState - מחזירה את GREEN כצבע שלה ומדפיסה את Go! Light is green. כאשר קוראים ל-next(), ואז עוברת ל-YellowState
    • YellowState - מחזירה את YELLOW כצבע שלה ומדפיסה את Caution! Light is yellow. כאשר קוראים ל-next(), ואז עוברת ל-RedState
    לאחר מכן יש ליצור מחלקת TrafficLight (ההקשר) שמתחילה במצב RedState. צריכה להיות לה מתודת setState() לשינוי המצב הנוכחי, מתודת getColor() שמעבירה את הקריאה למצב הנוכחי, ומתודת change() שקוראת ל-next() במצב הנוכחי.
  • main.dart: יש לייבא את קובץ הרמזור ולהדגים את תבנית המצב בפעולה. יש ליצור TrafficLight ולהדפיס את צבעו ההתחלתי. לאחר מכן יש לקרוא ל-change() שלוש פעמים, ולהדפיס את הצבע אחרי כל שינוי. כך ניתן לראות את הרמזור עובר בין כל המצבים וחוזר להתחלה.

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

הפלט הצפוי:

RED
Stop! Light is red.
GREEN
Go! Light is green.
YELLOW
Caution! Light is yellow.
RED

נסו בעצמכם

import 'traffic_light.dart';

void main() {
  // TODO: צרו מופע של TrafficLight
  
  // TODO: הדפיסו את הצבע ההתחלתי
  
  // TODO: קראו ל־change() והדפיסו את הצבע (3 פעמים כדי לעבור על כל המצבים)
  // שינוי ראשון: RED -> GREEN
  
  // שינוי שני: GREEN -> YELLOW
  
  // שינוי שלישי: YELLOW -> RED
}
quiz iconבחנו את עצמכם

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

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

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